# all files / files/utils/ StepDefinitionGenerator.js

100% Statements 101/101
100% Branches 24/24
100% Functions 22/22
100% Lines 46/46
2 statements, 4 branches Ignored

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123

    1
    1
    1
    1
    1
    1

    1

    1
    11

    8

    8

    8
    8

    1
    8

    12
    2

    7
    7

    2

    10

    1
    8

    8
    8

    8
    12
    12
    12
    12

    11
    11
    11

    1
    8

    8

    1
    12

    12
    11
    11
    11
    11
    11

    11

     'use strict';

    // Constants:
    import constants from '../../constants';
    const GIVEN_WHEN_THEN_REGEX = /^(Given|When|Then)/;
    const AND_BUT_REGEX = /^(And|But)/;
    const STUB_REGEX_REGEX = /this\.[Given|When|Then]*\(\/\^(.*?)\$\//;
    const NEW_LINE_REGEX = /\r\n|\n/;
    const NEW_LINES_REGEX = /(\r\n|\n){2}/;
    const STEP_DEFINITION_REGEX = /^this\.(Given|Then|When)[\s\S]*\}\);$/m;

    // Utilities:
    import _ from 'lodash';
    import Promise from 'bluebird';
    const childProcess = Promise.promisifyAll(require('child_process'));
    import path from 'path';

    // Dependencies:
    import esprima from 'esprima';
    import estemplate from 'estemplate';
    import fileStructure from '../../file-structure';
    import StepDefinitionFile from '../StepDefinitionFile';
    import stripcolorcodes from 'stripcolorcodes';

    export default class StepDefinitionGenerator {
        constructor (file) {
            this.file = file;
        }

        generate () {
            let { content, path } = this.file;
            let stepNames = extractStepNames(content);

            return childProcess.execAsync(`${constants.CUCUMBER_COMMAND} "${path}"`)
            .spread((result) => {
                console.log(result);
                return generateStepDefinitionFiles(stepNames, result);
            });
        }
    }

    function extractStepNames (feature) {
        return stripcolorcodes(feature)
        // Split on new-lines:
        .split(NEW_LINE_REGEX)
        // Remove whitespace:
        .map(line => line.trim())
        // Get out each step name:
        .filter((line) => GIVEN_WHEN_THEN_REGEX.test(line) || AND_BUT_REGEX.test(line))
        .map((stepName, index, stepNames) => {
            if (AND_BUT_REGEX.test(stepName)) {
                let previousType = _(stepNames)
                .take(index + 1)
                .reduceRight((p, n) => {
                    let type = n.match(GIVEN_WHEN_THEN_REGEX);
                    return p || _.last(type);
                }, null);
                return stepName.replace(AND_BUT_REGEX, previousType);
            } else {
                return stepName;
            }
        });
    }

    function generateStepDefinitionFiles (stepNames, result) {
        let existingFileNames = _(fileStructure.allFiles)
        .filter(file => file.path.match(new RegExp(constants.EXTENSIONS['step-definitions'])))
        .map(file => file.name)
        .value();

        let directory = fileStructure.structure.getDirectory(constants.STEP_DEFINITIONS);
        let stubs = splitResultToStubs(result);

        return Promise.map(stubs, (stub) => {
            let stubRegex = new RegExp(_.last(stub.match(STUB_REGEX_REGEX)));
            let stepName = _.find(stepNames, stepName => stubRegex.test(stepName));
            let fileData = generateStepDefinitionFile(existingFileNames, stub, stepName);
            if (fileData) {
                let { ast, fileName } = fileData;
                let filePath = path.join(directory.path, fileName + constants.EXTENSIONS['step-definitions']);
                let file = new StepDefinitionFile(filePath, directory);
                return file.save(ast);
            }
        });
    }

    function splitResultToStubs (result) {
        let pieces = stripcolorcodes(result)
        // Split on new-lines:
        .split(NEW_LINES_REGEX);
        // Filter out everything that isn't a step definition:
        return pieces.filter(piece => !!STEP_DEFINITION_REGEX.exec(piece.replace(/^\s+/, "")));
    }

    function generateStepDefinitionFile (existingFileNames, stub, name) {
        let fileName = name
        // Escape existing _s:
        .replace(/_/g, '__')
        // Replace / and \:
        .replace(/[\/\\]/g, '_')
        // Replace <s and >s:
        .replace(/</g, '_')
        .replace(/>/g, '_')
        // Replace ?, :, *, ". |:
        .replace(/[\?\:\*\"\|"]/g, '_')
        // Replace money:
        .replace(/\$\d+/g, '\$amount')
        // Replace numbers:
        .replace(/\d+/g, '\$number');

        if (!_.contains(existingFileNames, fileName)) {
            let template = 'module.exports = function () {%= body %};';
            let body = esprima.parse(stub).body;
            let ast = estemplate(template, { body });
            let meta = { name };
            ast.comments = [{
                type: 'Block',
                value: JSON.stringify(meta)
            }];
            return { ast, fileName };
        }
    }