Skip to content

Commit

Permalink
feat(testing): add ability to split jest tests
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz committed Apr 5, 2024
1 parent 4189a9c commit 495859a
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 617 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
"jest-environment-jsdom": "29.4.3",
"jest-environment-node": "^29.4.1",
"jest-resolve": "^29.4.1",
"jest-runtime": "^29.4.1",
"jest-util": "^29.4.1",
"js-tokens": "^4.0.0",
"js-yaml": "4.1.0",
Expand Down
88 changes: 87 additions & 1 deletion packages/jest/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ describe('@nx/jest/plugin', () => {
};

await tempFs.createFiles({
'proj/jest.config.js': '',
'proj/jest.config.js': `module.exports = {}`,
'proj/src/unit.spec.ts': '',
'proj/src/ignore.spec.ts': '',
'proj/project.json': '{}',
});
});
Expand Down Expand Up @@ -75,6 +77,90 @@ describe('@nx/jest/plugin', () => {
}
`);
});

it('should create test-ci targets based on jest.config.ts', async () => {
mockJestConfig(
{
coverageDirectory: '../coverage',
testMatch: ['**/*.spec.ts'],
testPathIgnorePatterns: ['ignore.spec.ts'],
},
context
);
const nodes = await createNodesFunction(
'proj/jest.config.js',
{
targetName: 'test',
ciTargetName: 'test-ci',
},
context
);

expect(nodes.projects.proj).toMatchInlineSnapshot(`
{
"root": "proj",
"targets": {
"test": {
"cache": true,
"command": "jest",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"jest",
],
},
],
"options": {
"cwd": "proj",
},
"outputs": [
"{workspaceRoot}/coverage",
],
},
"test-ci": {
"cache": true,
"dependsOn": [
"test-ci--src/unit.spec.ts",
],
"executor": "nx:noop",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"jest",
],
},
],
"outputs": [
"{workspaceRoot}/coverage",
],
},
"test-ci--src/unit.spec.ts": {
"cache": true,
"command": "jest src/unit.spec.ts",
"inputs": [
"default",
"^production",
{
"externalDependencies": [
"jest",
],
},
],
"options": {
"cwd": "proj",
},
"outputs": [
"{workspaceRoot}/coverage",
],
},
},
}
`);
});
});

function mockJestConfig(config: any, context: CreateNodesContext) {
Expand Down
70 changes: 54 additions & 16 deletions packages/jest/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
TargetConfiguration,
writeJsonFile,
} from '@nx/devkit';
import { dirname, join, relative, resolve } from 'path';
import { dirname, join, normalize, relative, resolve } from 'path';

import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { readConfig } from 'jest-config';
Expand All @@ -22,6 +21,7 @@ import { minimatch } from 'minimatch';

export interface JestPluginOptions {
targetName?: string;
ciTargetName?: string;
}

const cachePath = join(projectGraphCacheDirectory, 'jest.hash');
Expand Down Expand Up @@ -127,12 +127,6 @@ async function buildJestTargets(
resolve(context.workspaceRoot, configFilePath)
);

const targetDefaults = readTargetDefaultsForTarget(
options.targetName,
context.nxJsonConfiguration.targetDefaults,
'nx:run-commands'
);

const namedInputs = getNamedInputs(projectRoot, context);

const targets: Record<string, TargetConfiguration> = {};
Expand All @@ -144,14 +138,57 @@ async function buildJestTargets(
},
});

if (!targetDefaults?.cache) {
target.cache = true;
}
if (!targetDefaults?.inputs) {
target.inputs = getInputs(namedInputs);
}
if (!targetDefaults?.outputs) {
target.outputs = getOutputs(projectRoot, config, context);
const cache = (target.cache = true);
const inputs = (target.inputs = getInputs(namedInputs));
const outputs = (target.outputs = getOutputs(projectRoot, config, context));

if (options?.ciTargetName) {
// Resolve the version of `jest-runtime` that `jest` is using.
const jestPath = require.resolve('jest');
const jest = require(jestPath) as typeof import('jest');
// nx-ignore-next-line
const { default: Runtime } = require(require.resolve('jest-runtime', {
paths: [dirname(jestPath)],
// nx-ignore-next-line
})) as typeof import('jest-runtime');

const context = await Runtime.createContext(config.projectConfig, {
maxWorkers: 1,
watchman: false,
});

const source = new jest.SearchSource(context);

const specs = await source.getTestPaths(config.globalConfig);

const testPaths = new Set(specs.tests.map(({ path }) => path));

if (testPaths.size > 0) {
const dependsOn: string[] = [];

targets[options.ciTargetName] = {
executor: 'nx:noop',
cache: true,
inputs,
outputs,
dependsOn,
};

for (const testPath of testPaths) {
const relativePath = normalize(relative(projectRoot, testPath));
const targetName = `${options.ciTargetName}--${relativePath}`;
dependsOn.push(targetName);
targets[targetName] = {
command: `jest ${relativePath}`,
cache,
inputs,
outputs,
options: {
cwd: projectRoot,
},
};
}
}
}

return targets;
Expand Down Expand Up @@ -200,6 +237,7 @@ function getOutputs(

return outputs;
}

function normalizeOptions(options: JestPluginOptions): JestPluginOptions {
options ??= {};
options.targetName ??= 'test';
Expand Down
Loading

0 comments on commit 495859a

Please sign in to comment.