jest-bdd-generator
Version:
Jest code generator empowered with Gherkin and BDD
597 lines (596 loc) • 22.9 kB
JavaScript
;
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