UNPKG

@nx/jest

Version:

The Nx Plugin for Jest contains executors and generators allowing your workspace to use the powerful Jest testing capabilities.

211 lines (210 loc) • 8.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addOrUpdateProperty = addOrUpdateProperty; exports.removeProperty = removeProperty; exports.jestConfigObjectAst = jestConfigObjectAst; exports.jestConfigObject = jestConfigObject; const devkit_1 = require("@nx/devkit"); const vm_1 = require("vm"); const path_1 = require("path"); const ensure_typescript_1 = require("@nx/js/src/utils/typescript/ensure-typescript"); let tsModule; function makeTextToInsert(value, precedingCommaNeeded) { return `${precedingCommaNeeded ? ',' : ''}${value}`; } function findPropertyAssignment(object, propertyName) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } return object.properties.find((prop) => { if (!tsModule.isPropertyAssignment(prop)) { return false; } const propNameText = prop.name.getText(); if (propNameText.match(/^["'].+["']$/g)) { return JSON.parse(propNameText.replace(/'/g, '"')) === propertyName; } return propNameText === propertyName; }); } function addOrUpdateProperty(tree, object, properties, value, path) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const { SyntaxKind } = tsModule; const propertyName = properties.shift(); const propertyAssignment = findPropertyAssignment(object, propertyName); const originalContents = tree.read(path, 'utf-8'); if (propertyAssignment) { if (propertyAssignment.initializer.kind === SyntaxKind.StringLiteral || propertyAssignment.initializer.kind === SyntaxKind.NumericLiteral || propertyAssignment.initializer.kind === SyntaxKind.FalseKeyword || propertyAssignment.initializer.kind === SyntaxKind.TrueKeyword) { const updatedContents = (0, devkit_1.applyChangesToString)(originalContents, [ { type: devkit_1.ChangeType.Delete, start: propertyAssignment.initializer.pos, length: propertyAssignment.initializer.getFullText().length, }, { type: devkit_1.ChangeType.Insert, index: propertyAssignment.initializer.pos, text: value, }, ]); tree.write(path, updatedContents); return; } if (propertyAssignment.initializer.kind === SyntaxKind.ArrayLiteralExpression) { const arrayLiteral = propertyAssignment.initializer; if (arrayLiteral.elements.some((element) => { return element.getText().replace(/'/g, '"') === value; })) { return []; } if (arrayLiteral.elements.length === 0) { const updatedContents = (0, devkit_1.applyChangesToString)(originalContents, [ { type: devkit_1.ChangeType.Insert, index: arrayLiteral.elements.end, text: value, }, ]); tree.write(path, updatedContents); return; } else { const text = makeTextToInsert(value, arrayLiteral.elements.length !== 0 && !arrayLiteral.elements.hasTrailingComma); const updatedContents = (0, devkit_1.applyChangesToString)(originalContents, [ { type: devkit_1.ChangeType.Insert, index: arrayLiteral.elements.end, text, }, ]); tree.write(path, updatedContents); return; } } else if (propertyAssignment.initializer.kind === SyntaxKind.ObjectLiteralExpression) { return addOrUpdateProperty(tree, propertyAssignment.initializer, properties, value, path); } } else { if (propertyName === undefined) { throw new Error(`Please use dot delimited paths to update an existing object. Eg. object.property `); } const text = makeTextToInsert(`${JSON.stringify(propertyName)}: ${value}`, object.properties.length !== 0 && !object.properties.hasTrailingComma); const updatedContents = (0, devkit_1.applyChangesToString)(originalContents, [ { type: devkit_1.ChangeType.Insert, index: object.properties.end, text, }, ]); tree.write(path, updatedContents); return; } } function removeProperty(object, properties) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const propertyName = properties.shift(); const propertyAssignment = findPropertyAssignment(object, propertyName); if (propertyAssignment) { if (properties.length > 0 && propertyAssignment.initializer.kind === tsModule.SyntaxKind.ObjectLiteralExpression) { return removeProperty(propertyAssignment.initializer, properties); } return propertyAssignment; } else { return null; } } function isModuleExport(node) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } return (tsModule.isExpressionStatement(node) && node.expression?.kind && tsModule.isBinaryExpression(node.expression) && node.expression.left.getText() === 'module.exports' && node.expression.operatorToken?.kind === tsModule.SyntaxKind.EqualsToken); } function isDefaultExport(node) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } return (tsModule.isExportAssignment(node) && node.expression?.kind && tsModule.isObjectLiteralExpression(node.expression) && node.getText().startsWith('export default')); } /** * Should be used to get the jest config object as AST */ function jestConfigObjectAst(fileContent) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const sourceFile = tsModule.createSourceFile('jest.config.ts', fileContent, tsModule.ScriptTarget.Latest, true); const exportStatement = sourceFile.statements.find((statement) => isModuleExport(statement) || isDefaultExport(statement)); let ast; if (tsModule.isExpressionStatement(exportStatement)) { const moduleExports = exportStatement.expression; if (!moduleExports) { throw new Error(` The provided jest config file does not have the expected 'module.exports' expression. See https://jestjs.io/docs/en/configuration for more details.`); } ast = moduleExports.right; } else if (tsModule.isExportAssignment(exportStatement)) { const defaultExport = exportStatement.expression; if (!defaultExport) { throw new Error(` The provided jest config file does not have the expected 'export default' expression. See https://jestjs.io/docs/en/configuration for more details.`); } ast = defaultExport; } if (!ast) { throw new Error(` The provided jest config file does not have the expected 'module.exports' or 'export default' expression. See https://jestjs.io/docs/en/configuration for more details.`); } if (!tsModule.isObjectLiteralExpression(ast)) { throw new Error(`The 'export default' or 'module.exports' expression is not an object literal.`); } return ast; } /** * Returns the jest config object * @param host * @param path */ function jestConfigObject(host, path) { const __filename = (0, path_1.join)(host.root, path); const contents = host.read(path, 'utf-8'); let module = { exports: {} }; // transform the export default syntax to module.exports // this will work for the default config, but will break if there are any other ts syntax // TODO(caleb): use the AST to transform back to the module.exports syntax so this will keep working // or deprecate and make a new method for getting the jest config object const forcedModuleSyntax = contents.replace(/export\s+default/, 'module.exports ='); // Run the contents of the file with some stuff from this current context // The module.exports will be mutated by the contents of the file... (0, vm_1.runInContext)(forcedModuleSyntax, (0, vm_1.createContext)({ module, require, process, __filename, __dirname: (0, path_1.dirname)(__filename), })); // TODO: jest actually allows defining configs with async functions... we should be able to read that... return module.exports; }