UNPKG

jest-bdd-generator

Version:

Jest code generator empowered with Gherkin and BDD

597 lines (596 loc) 22.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var transpiler_exports = {}; __export(transpiler_exports, { Transpile: () => Transpile, customTransformerFactory: () => customTransformerFactory }); module.exports = __toCommonJS(transpiler_exports); var ts = __toESM(require("typescript")); function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _object_spread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys2 = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys2 = ownKeys2.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys2.forEach(function(key) { _define_property(target, key, source[key]); }); } return target; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function(sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _object_spread_props(target, source) { source = source != null ? source : {}; if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function(key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function isTsNode(node) { const v = node; return !!v && !!(ts.SyntaxKind[v.kind] && ts.NodeFlags[v.flags]); } const compareNodesIgnores = [ "pos", "end", "id", "parent", "nextContainer" ]; function compareNodes(node1, node2) { const keys = Object.keys(node1); for (let i = 0; i < keys.length; i++) { const k = keys[i]; if (compareNodesIgnores.indexOf(k) !== -1) { continue; } const v1 = node1[k]; const v2 = node2[k]; if (typeof v1 === "string") { if (typeof v2 !== "string" || v2 !== v1) { return false; } } else if (typeof v1 === "number") { if (typeof v2 !== "number" || v2 !== v1) { return false; } } else if (Array.isArray(v1)) { if (!Array.isArray(v2) || v2.length !== v1.length) { return false; } for (let j = 0; j < v1.length; j++) { if (!compareNodes(v1[j], v2[j])) { return false; } } } else if (isTsNode(v1)) { if (!isTsNode(v2)) { return false; } if (!compareNodes(v1, v2)) { return false; } } } return true; } function searchCallName(node) { if (ts.isParenthesizedExpression(node) && ts.isBinaryExpression(node.expression)) { return searchCallName(node.expression.right); } else if (ts.isPropertyAccessExpression(node)) { var _searchCallName; return (_searchCallName = searchCallName(node.expression)) !== null && _searchCallName !== void 0 ? _searchCallName : node.name.escapedText; } else if (ts.isIdentifier(node)) { return node.escapedText; } else if (ts.isCallExpression(node) || ts.isAwaitExpression(node)) { return searchCallName(node.expression); } else if (ts.isTaggedTemplateExpression(node)) { return searchCallName(node.tag); } } function traverseArguments(arrArgsMap, callExpression, context) { const contextCallExpression = {}; for (let argIndex = 0; argIndex < arrArgsMap.arguments.length; argIndex++) { const argMapTo = arrArgsMap.arguments[argIndex]; const currentArgument = callExpression.arguments[argIndex]; if (argMapTo.callback) { argMapTo.callback(currentArgument, callExpression, contextCallExpression); } if (argMapTo.searchCallSchema) { if (ts.isArrowFunction(currentArgument) || ts.isFunctionExpression(currentArgument)) { traverseChildren(argMapTo.searchCallSchema, currentArgument.body, context); } } } } function traverseChildren(signatures, node, context) { ts.visitEachChild(node, (subNode) => { if (ts.isExpressionStatement(subNode) && ts.isCallExpression(subNode.expression)) { const callExpression = subNode.expression; const name = searchCallName(callExpression.expression); for (let i = 0; i < signatures.length; i++) { const arrArgsMap = signatures[i]; if (typeof arrArgsMap === "function") { continue; } if (name === arrArgsMap.keyword) { traverseArguments(arrArgsMap, callExpression, context); if (arrArgsMap.chain) { let subExpression = callExpression; while (subExpression.expression) { if (ts.isPropertyAccessExpression(subExpression.expression)) { const nameChain = subExpression.expression.name.escapedText; const chainSchema = arrArgsMap.chain.find((schemaKey) => schemaKey.keyword === nameChain); if (chainSchema) { traverseArguments(chainSchema, subExpression, context); } return subNode; } subExpression = subExpression.expression; } } } } } }, context); } const customTransformerFactory = (searchSchema, self) => (context) => { return { transformBundle: (node) => node, transformSourceFile: (node) => { self.sourceFile = node; if (node.fileName.match(/\.test\.ts/)) { traverseChildren(searchSchema, node, context); } return node; } }; }; class Transpile { get steps() { return this.output.map((s) => { const { key, value, host, parent, pos, examples } = s; return { key, value, host, parent, pos, examples }; }); } get uniqueSteps() { const stepsCopied = this.steps; return stepsCopied.filter((s, i) => stepsCopied.findIndex((f) => f.key === s.key && f.value === s.value) === i); } set input(input) { var _sourceMap_sourcesContent; const sourceMap = this.dealwithSourceMap(input); if (sourceMap === null || sourceMap === void 0 ? void 0 : (_sourceMap_sourcesContent = sourceMap.sourcesContent) === null || _sourceMap_sourcesContent === void 0 ? void 0 : _sourceMap_sourcesContent[0]) { this.source = sourceMap.sourcesContent[0]; } else { this.source = input; } } get input() { return this.source; } parsePosition(pos) { if (this.sourceFile) { const { line, character } = this.sourceFile.getLineAndCharacterOfPosition(pos); return { line, character, pos }; } else { throw new Error(); } } groupToScenario() { const scenarios = this.output.filter((step) => step.key === "Scenario"); this.output.forEach((step) => { if (step.key === "Scenario") { return; } switch (step.key) { case "Given": case "When": case "Then": { const parent = scenarios.find( (s) => s.pos.start.pos < step.pos.start.pos && s.pos.end.pos >= step.pos.end.pos //s.pos.start.line < step.pos.start.line && s.pos.end.line > step.pos.end.line, ); if (!parent) { return; } step.parent = parent.value; break; } case "Examples": { const parent = scenarios.find((s) => s.value === step.value && s.key === "Scenario"); if (!parent) { return; } step.parent = parent.value; parent.examples = step.examples; break; } default: break; } }); } getDeclaration(declaration) { if (ts.isObjectBindingPattern(declaration.name)) { return declaration.name.elements.map((a) => this.getDeclaration(a)).flat(); } else if (ts.isIdentifier(declaration.name)) { return [ declaration.name.escapedText.toString() ]; } return []; } generateComments() { var _this_possibleStep; const possibleStep = (_this_possibleStep = this.possibleStep) !== null && _this_possibleStep !== void 0 ? _this_possibleStep : []; const output = []; let lastIndex = 0; possibleStep.reduce((prev, step, i, self) => { const start = step.pos.start.pos; if (step.key === "Matched Scenario") { return prev; } if (start < lastIndex) { console.error("warning: Overlap!"); return prev; } const end = step.pos.end.pos; const stepDuplicated = prev.find((s) => s.key === step.key && s.value === step.value); if (stepDuplicated) { return prev; } output.push(this.input.substring(lastIndex, start), ` `, ...step.value.split("\n").map((v) => ` //@${step.key}${v}`), this.input.substring(start, end), ` //# end of matched [@${step.value}] from [${step.parent}] Scenario `); lastIndex = end; return prev; }, []); output.push(this.input.substring(lastIndex)); return output.join(""); } generateFromKnownSteps(block) { const mapStatementsMatch = this.output.map((step) => ({ key: step.key, value: step.value, idxStatement: 0 })); const matched = []; block.statements.forEach((statement, idxCurrentStatement) => { mapStatementsMatch.forEach((map, idx) => { var _step_sourceCode_statements, _step_sourceCode; if (map.idxStatement < 0) { return; } const step = this.output[idx]; const statementToCompare = step === null || step === void 0 ? void 0 : (_step_sourceCode = step.sourceCode) === null || _step_sourceCode === void 0 ? void 0 : (_step_sourceCode_statements = _step_sourceCode.statements) === null || _step_sourceCode_statements === void 0 ? void 0 : _step_sourceCode_statements[map.idxStatement]; if (!statementToCompare) { return; } const isCurrentStatMatch = compareNodes(statement, statementToCompare); if (isCurrentStatMatch) { var _step_sourceCode_statements1, _step_sourceCode1; map.idxStatement++; if (map.idxStatement === ((_step_sourceCode1 = step.sourceCode) === null || _step_sourceCode1 === void 0 ? void 0 : (_step_sourceCode_statements1 = _step_sourceCode1.statements) === null || _step_sourceCode_statements1 === void 0 ? void 0 : _step_sourceCode_statements1.length)) { const fullyMatched = { key: map.key, value: `${map.key} ${map.value}`, startIdx: idxCurrentStatement - map.idxStatement + 1, endIdx: idxCurrentStatement, from: [ step ] }; const duplication = matched.find((m) => m.startIdx === fullyMatched.startIdx && m.endIdx === fullyMatched.endIdx); if (duplication) { if (duplication.key === fullyMatched.key && duplication.value === fullyMatched.value) { duplication.from.push(step); } else { duplication.value += ` ${fullyMatched.value}`; } } else { matched.push(fullyMatched); } map.idxStatement = 0; } } else { map.idxStatement = 0; } }); }); return matched.reduce((dedupe, match, idx, self) => { var _match_from__sourceCode, _match_from__sourceCode1; const firstStatement = block.statements[match.startIdx]; const lastStatement = block.statements[match.endIdx]; const statements = block.statements.slice(match.startIdx, match.endIdx + 1); var _match_from__sourceCode_imports, _match_from__sourceCode_exports; dedupe.push({ key: ``, value: match.value, pos: { start: this.parsePosition(firstStatement.pos), end: this.parsePosition(lastStatement.end) }, parent: match.from.map((step) => step.host).join(", "), sourceCode: { statements, fullText: statements.map((s) => s.getFullText()).join(""), imports: [ ...(_match_from__sourceCode_imports = (_match_from__sourceCode = match.from[0].sourceCode) === null || _match_from__sourceCode === void 0 ? void 0 : _match_from__sourceCode.imports) !== null && _match_from__sourceCode_imports !== void 0 ? _match_from__sourceCode_imports : [] ], exports: [ ...(_match_from__sourceCode_exports = (_match_from__sourceCode1 = match.from[0].sourceCode) === null || _match_from__sourceCode1 === void 0 ? void 0 : _match_from__sourceCode1.exports) !== null && _match_from__sourceCode_exports !== void 0 ? _match_from__sourceCode_exports : [] ] } }); return dedupe; }, []); } getComments(block, _lastItem) { const ret = []; let lastItem = _lastItem; block.statements.forEach((fnHandler) => { const commentRange = ts.getLeadingCommentRanges(this.input, fnHandler.getFullStart()); commentRange === null || commentRange === void 0 ? void 0 : commentRange.forEach((range) => { const strComment = this.input.substring(range.pos, range.end); const keywords = strComment === null || strComment === void 0 ? void 0 : strComment.match(/^\s*?\/\/\s*?@(When|Then|Given) (.+[^\s])\s*?$/); if (!keywords) { return strComment; } ret.push({ key: keywords[1], value: keywords[2], pos: { start: this.parsePosition(range.pos), end: this.parsePosition(fnHandler.end) }, sourceCode: { fullText: "", imports: [], exports: [], statements: [] } }); return strComment; }); var _ret_; lastItem = (_ret_ = ret[ret.length - 1]) !== null && _ret_ !== void 0 ? _ret_ : lastItem; if (lastItem) { var _lastItem_sourceCode_statements; lastItem.sourceCode = lastItem.sourceCode || { fullText: "", statements: [] }; (_lastItem_sourceCode_statements = lastItem.sourceCode.statements) === null || _lastItem_sourceCode_statements === void 0 ? void 0 : _lastItem_sourceCode_statements.push(fnHandler); if (ts.isExpressionStatement(fnHandler)) { var _lastItem_sourceCode; const arrInputs = [ searchCallName(fnHandler.expression) ].flat(); lastItem.sourceCode.imports = ((_lastItem_sourceCode = lastItem.sourceCode) === null || _lastItem_sourceCode === void 0 ? void 0 : _lastItem_sourceCode.imports) || []; lastItem.sourceCode.imports.push(...arrInputs.filter((i) => { var _lastItem_sourceCode_imports, _lastItem_sourceCode2; return !(lastItem === null || lastItem === void 0 ? void 0 : (_lastItem_sourceCode2 = lastItem.sourceCode) === null || _lastItem_sourceCode2 === void 0 ? void 0 : (_lastItem_sourceCode_imports = _lastItem_sourceCode2.imports) === null || _lastItem_sourceCode_imports === void 0 ? void 0 : _lastItem_sourceCode_imports.includes(i)); })); } else if (ts.isVariableStatement(fnHandler)) { const arrOutputs = fnHandler.declarationList.declarations.map((declaration) => this.getDeclaration(declaration)); lastItem.sourceCode.exports = lastItem.sourceCode.exports || []; lastItem.sourceCode.exports.push(...arrOutputs.flat().filter((i) => { var _lastItem_sourceCode_exports, _lastItem_sourceCode2; return !(lastItem === null || lastItem === void 0 ? void 0 : (_lastItem_sourceCode2 = lastItem.sourceCode) === null || _lastItem_sourceCode2 === void 0 ? void 0 : (_lastItem_sourceCode_exports = _lastItem_sourceCode2.exports) === null || _lastItem_sourceCode_exports === void 0 ? void 0 : _lastItem_sourceCode_exports.includes(i)); })); } } if (ts.isIfStatement(fnHandler) && ts.isBlock(fnHandler.thenStatement)) { ret.push(...this.getComments(fnHandler.thenStatement, lastItem)); } }); return ret.flat(); } dealwithSourceMap(input) { return {}; } transpile(input, options = {}) { var _options_fileName; if ((_options_fileName = options.fileName) === null || _options_fileName === void 0 ? void 0 : _options_fileName.match(/\.test\.ts$/)) { this.input = input; const raw = ts.transpileModule(this.input, _object_spread_props(_object_spread({}, options), { transformers: { before: [ customTransformerFactory(this.searchSchema, this) ] }, compilerOptions: { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.React, isolatedModules: true, sourceMap: true } })); const output = { outputText: this._prepareOutput(), sourceMapText: raw.sourceMapText }; return output; } else { return ts.transpileModule(input, _object_spread_props(_object_spread({}, options), { compilerOptions: { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.React, isolatedModules: true, sourceMap: true } })); } } _prepareOutput() { this.groupToScenario(); return this.outputCode(); } outputCode() { return ""; } constructor(searchSchema) { _define_property(this, "searchSchema", void 0); _define_property(this, "source", ""); _define_property(this, "sourceFile", void 0); _define_property(this, "output", []); _define_property(this, "possibleStep", []); _define_property(this, "callbackOnStringArgumentFactory", (name) => { return (expressionOfArg, callExpression, context) => { let argValue; if (ts.isStringLiteral(expressionOfArg) || ts.isNoSubstitutionTemplateLiteral(expressionOfArg)) { argValue = expressionOfArg.text; } else if (ts.isTemplateExpression(expressionOfArg)) { argValue = expressionOfArg.head.text + expressionOfArg.templateSpans.map((sp) => `<${sp.expression.getText()}>${sp.literal.text}`).join(""); } else { return; } let k, v; const matchedValue = argValue.match(/^(When|Then|Given) (.+)$/); if (matchedValue) { k = matchedValue[1]; v = matchedValue[2]; } else { k = name; v = argValue; } const fnArgument = callExpression.arguments.find((n) => ts.isFunctionLike(n) && ts.isBlock(n.body)); const fnArgumentBody = fnArgument === null || fnArgument === void 0 ? void 0 : fnArgument.body; var _fnArgumentBody_statements; const step = { key: k, value: v, pos: { start: this.parsePosition(expressionOfArg.pos), end: this.parsePosition(expressionOfArg.end) }, sourceCode: { fullText: callExpression.getFullText(), // ["describe", "(", ["some title", ",", "() => {....}"], ")"] statements: [ ...(_fnArgumentBody_statements = fnArgumentBody === null || fnArgumentBody === void 0 ? void 0 : fnArgumentBody.statements) !== null && _fnArgumentBody_statements !== void 0 ? _fnArgumentBody_statements : [] ] }, host: argValue }; this.output.push(step); context.stepOfCurrentFunction = step; context.host = argValue; }; }); _define_property(this, "callbackOnFnArgument", (expressionOfArg, callExpression, callContext) => { const steps = []; const host = callContext.host; const stepOfCurrentFunction = callContext.stepOfCurrentFunction; const param = expressionOfArg; if (!param.body) { console.error("Argument type is not functionExpression", ts.SyntaxKind[param.kind]); return; } stepOfCurrentFunction.pos.start = this.parsePosition(param.body.pos); stepOfCurrentFunction.pos.end = this.parsePosition(param.body.end); steps.push(...this.getComments(param.body, stepOfCurrentFunction)); if (steps.length > 0) { steps[steps.length - 1].pos.end = _object_spread({}, stepOfCurrentFunction.pos.end); if (stepOfCurrentFunction.key !== "Scenario") { stepOfCurrentFunction.pos.end = this.parsePosition(steps[0].pos.start.pos - 1); } steps[0].host = host; for (let i = 1; i < steps.length; i++) { steps[i].host = host; const startCurrentStep = steps[i].pos.start; const posCurrentStart = startCurrentStep.pos - 1; steps[i - 1].pos.end = this.parsePosition(posCurrentStart); } } else { this.possibleStep.push(...this.generateFromKnownSteps(param.body)); } this.output.push(...steps); }); if (searchSchema) { this.searchSchema = searchSchema; } } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Transpile, customTransformerFactory }); //# sourceMappingURL=index.js.map