@teambit/jest
Version:
331 lines (318 loc) • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JestTester = void 0;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = require("fs-extra");
_fsExtra = function () {
return data;
};
return data;
}
function _normalizePath() {
const data = _interopRequireDefault(require("normalize-path"));
_normalizePath = function () {
return data;
};
return data;
}
function _minimatch() {
const data = _interopRequireDefault(require("minimatch"));
_minimatch = function () {
return data;
};
return data;
}
function _lodash() {
const data = require("lodash");
_lodash = function () {
return data;
};
return data;
}
function _comlink() {
const data = require("comlink");
_comlink = function () {
return data;
};
return data;
}
function _tester() {
const data = require("@teambit/tester");
_tester = function () {
return data;
};
return data;
}
function _testsResults() {
const data = require("@teambit/tests-results");
_testsResults = function () {
return data;
};
return data;
}
function _jestMessageUtil() {
const data = require("jest-message-util");
_jestMessageUtil = function () {
return data;
};
return data;
}
function _component() {
const data = require("@teambit/component");
_component = function () {
return data;
};
return data;
}
function _error() {
const data = require("./error");
_error = function () {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // import { Environment } from '@teambit/envs';
// import { EnvPolicyConfigObject, PeersAutoDetectPolicy } from '@teambit/dependency-resolver';
class JestTester {
constructor(id, jestConfig, jestModulePath, jestWorker, logger, opts = {}) {
this.id = id;
this.jestConfig = jestConfig;
this.jestModulePath = jestModulePath;
this.jestWorker = jestWorker;
this.logger = logger;
this.opts = opts;
_defineProperty(this, "jestModule", void 0);
_defineProperty(this, "configPath", this.jestConfig);
_defineProperty(this, "displayName", 'Jest');
_defineProperty(this, "_callback", void 0);
// eslint-disable-next-line global-require,import/no-dynamic-require
this.jestModule = require(jestModulePath);
}
displayConfig() {
return (0, _fsExtra().readFileSync)(this.jestConfig, 'utf8');
}
version() {
return this.jestModule.getVersion();
}
attachTestsToComponent(testerContext, testResult) {
return _component().ComponentMap.as(testerContext.components, component => {
const componentPatternValue = testerContext.patterns.get(component);
if (!componentPatternValue) return undefined;
const [currComponent, patternEntry] = componentPatternValue;
const resolvedPatterns = this.resolveComponentPattern(currComponent, patternEntry, testerContext);
return testResult.filter(test => resolvedPatterns.some(resolvedPattern => (0, _minimatch().default)(test.testFilePath, resolvedPattern) ||
// on Windows, `test.testFilePath` has only single black slashes, while `resolvedPattern` has double black slashes.
(0, _minimatch().default)((0, _normalizePath().default)(test.testFilePath), (0, _normalizePath().default)(resolvedPattern))));
});
}
buildTestsObj(aggregatedResult, components, testerContext, config) {
const testsSuiteResult = components.toArray().map(([component, testsFiles]) => {
if (!testsFiles) return undefined;
if (testsFiles?.length === 0) return undefined;
const errors = this.getErrors(testsFiles);
const tests = testsFiles.map(test => {
const testResults = test.testResults.map(testResult => {
const error = (0, _jestMessageUtil().formatResultsErrors)([testResult], config, {
noStackTrace: true
}) || undefined;
const isFailure = testResult.status === 'failed';
return new (_testsResults().TestResult)(testResult.ancestorTitles, testResult.title, testResult.status, testResult.duration, isFailure ? undefined : error, isFailure ? error : undefined);
});
const filePath = (0, _path().basename)(test.testFilePath);
const getError = () => {
if (!test.testExecError) return undefined;
if (testerContext.watch) {
// for some reason, during watch ('bit start'), if a file has an error, the `test.testExecError` is `{}`
// (an empty object). the failureMessage contains the stringified error.
// @todo: consider to always use the failureMessage, regardless the context.watch.
return new (_error().JestError)(test.failureMessage);
}
return new (_error().JestError)(test.testExecError?.message, test.testExecError?.stack);
};
const error = getError();
return new (_testsResults().TestsFiles)(filePath, testResults, test.numPassingTests, test.numFailingTests, test.numPendingTests, test.perfStats.runtime, test.perfStats.slow, error);
});
return {
componentId: component.id,
results: new (_testsResults().TestsResult)(tests, aggregatedResult.success, aggregatedResult.startTime),
errors
};
});
return (0, _lodash().compact)(testsSuiteResult);
}
getErrors(testResult) {
return testResult.reduce((errors, test) => {
if (test.testExecError) {
const {
message,
stack,
code,
type
} = test.testExecError;
errors.push(new (_error().JestError)(message, stack, code, type));
} else if (test.failureMessage) {
errors.push(new (_error().JestError)(test.failureMessage));
}
return errors;
}, []);
}
async onTestRunComplete(callback) {
this._callback = callback;
}
async test(context) {
// const envRootDir = context.envRuntime.envAspectDefinition.aspectPath;
const config = {
// Setting the rootDir to the env root dir to make sure we can resolve all the jest presets/plugins
// from the env context
// rootDir: envRootDir,
// TODO: set it to envRootDir and make sure we can make the --coverage to work
// with the current value as context.rootPath it will probably won't work correctly when using rootComponents:true (maybe even won't work at all)
// TODO: when changing to envRootDir we have some issues with the react-native tests. so once changed again, it needs to be validated.
rootDir: context.rootPath,
// Setting the roots (where to search for spec files) to the root path (either workspace or capsule root)
// TODO: consider change this to be an array of the components running dir.
// TODO: aka: in the workspace it will be something like <ws>/node_modules/<comp-package-name>/node_modules/<comp-package-name>
// TODO: see dependencyResolver.getRuntimeModulePath (this will make sure the peer deps resolved correctly)
// TODO: (@GiladShoham - when trying to set it to this paths, jest ignores it probably because the paths contains "node_modules"
// TODO: trying to set the https://jestjs.io/docs/27.x/configuration#testpathignorepatterns-arraystring to something else (as it contain node_modules by default)
// TODO: didn't help)
roots: [context.rootPath]
};
// eslint-disable-next-line no-console
console.warn = message => {
this.logger.warn(message);
};
if (context.debug) {
config.debug = true;
config.runInBand = true;
}
if (context.coverage) config.coverage = true;
config.runInBand = true;
if (context.watch) {
config.watchAll = true;
config.noCache = true;
}
// eslint-disable-next-line global-require,import/no-dynamic-require
const jestConfig = require(this.jestConfig);
// TODO: rollback this for now, as it makes issues.
// TODO: it's mostly relevant for when the root components feature is enabled.
// TODO: we might want to enable it only on that case (along with setting the env root dir as the root dir, above)
// const moduleNameMapper = await this.calculateModuleNameMapper(
// context.env,
// context.rootPath,
// context.additionalHostDependencies
// );
// jestConfig.moduleNameMapper = Object.assign({}, jestConfig.moduleNameMapper || {}, moduleNameMapper);
const jestConfigWithSpecs = Object.assign(jestConfig, {
testMatch: this.patternsToArray(context)
});
const withEnv = Object.assign(jestConfigWithSpecs, config);
const testsOutPut = await this.jestModule.runCLI(withEnv, [this.jestConfig]);
const testResults = testsOutPut.results.testResults;
const componentsWithTests = this.attachTestsToComponent(context, testResults);
const componentTestResults = this.buildTestsObj(testsOutPut.results, componentsWithTests, context, jestConfigWithSpecs);
return new (_tester().Tests)(componentTestResults);
}
async watch(context) {
// eslint-disable-next-line
return new Promise(async resolve => {
const workerApi = this.jestWorker.initiate(context.ui ? {
stdout: true,
stderr: true,
stdin: true
} : {
stdout: false,
stderr: false,
stdin: false
});
// eslint-disable-next-line
const jestConfig = require(this.jestConfig);
const envRootDir = context.envRuntime.envAspectDefinition?.aspectPath;
if (!envRootDir) {
this.logger.warn(`jest tester, envRootDir is not defined, for env ${context.envRuntime.id}`);
}
const jestConfigWithSpecs = Object.assign(jestConfig, {
testMatch: this.patternsToArray(context)
});
try {
const cbFn = (0, _comlink().proxy)(results => {
if (!this._callback) return;
const testResults = results.testResults;
const componentsWithTests = this.attachTestsToComponent(context, testResults);
const componentTestResults = this.buildTestsObj(results, componentsWithTests, context, jestConfigWithSpecs);
const globalErrors = this.getErrors(testResults);
const watchTestResults = {
loading: false,
errors: globalErrors,
components: componentTestResults
};
this._callback(watchTestResults);
resolve(watchTestResults);
});
// eslint-disable-next-line @typescript-eslint/no-floating-promises
await workerApi.onTestComplete(cbFn);
await workerApi.watch(this.jestConfig, this.patternsToArray(context), context.rootPath, this.jestModulePath, envRootDir);
} catch (err) {
this.logger.error('jest.tester.watch() caught an error', err);
}
});
}
// private async calculateModuleNameMapper(
// env: Environment,
// rootPath: string,
// additionalHostDependencies?: string[]
// ): Promise<Record<string, Array<string>>> {
// const peerDepsConfig: EnvPolicyConfigObject = await env.getDependencies();
// const peersAutoDetectPolicy = new PeersAutoDetectPolicy(peerDepsConfig.peers || []);
// const peers = Object.keys(peerDepsConfig.peerDependencies || {}).concat(peersAutoDetectPolicy?.names);
// const depsToMap = peers.concat(additionalHostDependencies || []);
// /**
// * Try to resolve the dependency from the rootDir (the env dir) or from the root path (workspace/capsule root)
// */
// const mappedValues = ['<rootDir>/node_modules/$1', `${rootPath}/node_modules/$1`];
// const moduleNameMapper = depsToMap.reduce((acc, peerName) => {
// const keyName = `^(${peerName})$`;
// acc[keyName] = mappedValues;
// const internalPathKeyName = `^(${peerName}/.*)$`;
// acc[internalPathKeyName] = mappedValues;
// return acc;
// }, {});
// return moduleNameMapper;
// }
patternsToArray(context) {
return (0, _lodash().flatten)(context.patterns.toArray().map(([component, patternEntry]) => {
return this.resolveComponentPattern(component, patternEntry, context);
}));
}
resolveComponentPattern(component, patternEntry, context) {
if (this.opts.resolveSpecPaths) {
return this.opts.resolveSpecPaths(component, context);
}
const customPatterns = this.opts.patterns;
// If pattern were provided to the specific instance of the tester, use them
if (customPatterns && !(0, _lodash().isEmpty)(customPatterns)) {
customPatterns.map(customPattern => {
const rootDirs = this.opts.roots || [patternEntry.componentDir];
return this.resolvePattern(customPattern, rootDirs);
});
}
return patternEntry.paths.map(p => p.path);
}
resolvePattern(pattern, rootDirs) {
return rootDirs.map(dir => (0, _path().resolve)(dir, pattern));
}
}
exports.JestTester = JestTester;
//# sourceMappingURL=jest.tester.js.map