From 3b40e8d29da882ee36f67dfd029b10958563d364 Mon Sep 17 00:00:00 2001 From: Tobias Binna Date: Wed, 2 Sep 2026 18:38:31 +0800 Subject: [PATCH] test: migrate workspace Jest targets to inference Closes #213 --- .github/workflows/ci-pr.yml | 1 + e2e/nx-forge-e2e/project.json | 17 ++-- nx.json | 60 +++++-------- .../{jest.config.ts => jest.config.cts} | 2 +- packages/nx-forge/project.json | 12 ++- packages/nx-forge/tsconfig.lib.json | 2 +- packages/nx-forge/tsconfig.spec.json | 2 +- project.json | 6 ++ .../docs/{jest.config.ts => jest.config.cts} | 2 +- tools/docs/project.json | 10 ++- tools/docs/tsconfig.lib.json | 2 +- tools/docs/tsconfig.spec.json | 2 +- tools/scripts/verify-jest-inference.mjs | 88 +++++++++++++++++++ 13 files changed, 150 insertions(+), 56 deletions(-) rename packages/nx-forge/{jest.config.ts => jest.config.cts} (94%) rename tools/docs/{jest.config.ts => jest.config.cts} (93%) create mode 100644 tools/scripts/verify-jest-inference.mjs diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 17461e6..c52eb56 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -49,6 +49,7 @@ jobs: cache: true - run: pnpm install --frozen-lockfile - run: pnpm nx affected --target=build --parallel=3 + - run: pnpm nx verify-jest-inference workspace - run: pnpm nx affected --target=test --parallel=2 env: NODE_OPTIONS: '--throw-deprecation' diff --git a/e2e/nx-forge-e2e/project.json b/e2e/nx-forge-e2e/project.json index 3107257..5ec1ac1 100644 --- a/e2e/nx-forge-e2e/project.json +++ b/e2e/nx-forge-e2e/project.json @@ -3,15 +3,20 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "projectType": "application", "sourceRoot": "e2e/nx-forge-e2e/src", + "tags": [], + "implicitDependencies": ["nx-forge"], "targets": { "e2e": { - "executor": "@nx/jest:jest", + "dependsOn": ["^build"], "options": { - "jestConfig": "e2e/nx-forge-e2e/jest.config.js" + "passWithNoTests": true }, - "dependsOn": ["^build"] + "configurations": { + "ci": { + "ci": true, + "coverage": true + } + } } - }, - "tags": [], - "implicitDependencies": ["nx-forge"] + } } diff --git a/nx.json b/nx.json index 896809b..6cb2d21 100644 --- a/nx.json +++ b/nx.json @@ -6,32 +6,10 @@ "$schema": "./node_modules/nx/schemas/nx-schema.json", "targetDefaults": { "build": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "production", - "^production" - ], + "dependsOn": ["^build"], + "inputs": ["production", "^production"], "cache": true }, - "@nx/jest:jest": { - "inputs": [ - "default", - "^production", - "{workspaceRoot}/jest.preset.js" - ], - "cache": true, - "options": { - "passWithNoTests": true - }, - "configurations": { - "ci": { - "ci": true, - "codeCoverage": true - } - } - }, "@nx/eslint:lint": { "inputs": [ "default", @@ -44,31 +22,39 @@ }, "@nx/js:tsc": { "cache": true, - "dependsOn": [ - "^build" - ], - "inputs": [ - "production", - "^production" - ] + "dependsOn": ["^build"], + "inputs": ["production", "^production"] } }, "namedInputs": { - "default": [ - "{projectRoot}/**/*", - "sharedGlobals" - ], + "default": ["{projectRoot}/**/*", "sharedGlobals"], "sharedGlobals": [], "production": [ "default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/tsconfig.spec.json", - "!{projectRoot}/jest.config.[jt]s", + "!{projectRoot}/jest.config.{cjs,mjs,js,cts,mts,ts}", "!{projectRoot}/.eslintrc.json", "!{projectRoot}/src/test-setup.[jt]s", "!{projectRoot}/eslint.config.mjs" ] }, "defaultBase": "main", - "analytics": false + "analytics": false, + "plugins": [ + { + "plugin": "@nx/jest/plugin", + "options": { + "targetName": "e2e" + }, + "include": ["e2e/nx-forge-e2e/**/*"] + }, + { + "plugin": "@nx/jest/plugin", + "options": { + "targetName": "test" + }, + "include": ["packages/nx-forge/**/*", "tools/docs/**/*"] + } + ] } diff --git a/packages/nx-forge/jest.config.ts b/packages/nx-forge/jest.config.cts similarity index 94% rename from packages/nx-forge/jest.config.ts rename to packages/nx-forge/jest.config.cts index a796b4a..a848b56 100644 --- a/packages/nx-forge/jest.config.ts +++ b/packages/nx-forge/jest.config.cts @@ -1,4 +1,4 @@ -export default { +module.exports = { displayName: 'nx-forge', globals: {}, transform: { diff --git a/packages/nx-forge/project.json b/packages/nx-forge/project.json index 976ed35..8fad99a 100644 --- a/packages/nx-forge/project.json +++ b/packages/nx-forge/project.json @@ -2,7 +2,6 @@ "name": "nx-forge", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/nx-forge/src", - "tags": [], "release": { "version": { "manifestRootsToUpdate": ["dist/{projectRoot}"], @@ -10,6 +9,7 @@ "fallbackCurrentVersionResolver": "disk" } }, + "tags": [], "targets": { "nx-release-publish": { "executor": "@nx/js:release-publish", @@ -60,10 +60,14 @@ "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/nx-forge"], "options": { - "jestConfig": "packages/nx-forge/jest.config.ts" + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "coverage": true + } } } } diff --git a/packages/nx-forge/tsconfig.lib.json b/packages/nx-forge/tsconfig.lib.json index f6be4d3..06081b7 100644 --- a/packages/nx-forge/tsconfig.lib.json +++ b/packages/nx-forge/tsconfig.lib.json @@ -7,5 +7,5 @@ "strict": true }, "include": ["**/*.ts"], - "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] + "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.cts"] } diff --git a/packages/nx-forge/tsconfig.spec.json b/packages/nx-forge/tsconfig.spec.json index f95b08f..67c4517 100644 --- a/packages/nx-forge/tsconfig.spec.json +++ b/packages/nx-forge/tsconfig.spec.json @@ -5,5 +5,5 @@ "module": "commonjs", "types": ["jest", "node"] }, - "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts", "jest.config.cts"] } diff --git a/project.json b/project.json index 8a48781..4124fb2 100644 --- a/project.json +++ b/project.json @@ -2,6 +2,12 @@ "name": "workspace", "$schema": "node_modules/nx/schemas/project-schema.json", "targets": { + "verify-jest-inference": { + "executor": "nx:run-commands", + "options": { + "command": "node tools/scripts/verify-jest-inference.mjs" + } + }, "local-registry": { "executor": "@nx/js:verdaccio", "options": { diff --git a/tools/docs/jest.config.ts b/tools/docs/jest.config.cts similarity index 93% rename from tools/docs/jest.config.ts rename to tools/docs/jest.config.cts index 1318024..cc682b2 100644 --- a/tools/docs/jest.config.ts +++ b/tools/docs/jest.config.cts @@ -1,4 +1,4 @@ -export default { +module.exports = { displayName: 'docs-tools', preset: '../../jest.preset.js', transform: { diff --git a/tools/docs/project.json b/tools/docs/project.json index f0ec1fd..512ba8e 100644 --- a/tools/docs/project.json +++ b/tools/docs/project.json @@ -41,10 +41,14 @@ "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { - "jestConfig": "tools/docs/jest.config.ts" + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "coverage": true + } } } } diff --git a/tools/docs/tsconfig.lib.json b/tools/docs/tsconfig.lib.json index 33eca2c..d1705e6 100644 --- a/tools/docs/tsconfig.lib.json +++ b/tools/docs/tsconfig.lib.json @@ -6,5 +6,5 @@ "types": ["node"] }, "include": ["src/**/*.ts"], - "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] + "exclude": ["jest.config.cts", "src/**/*.spec.ts", "src/**/*.test.ts"] } diff --git a/tools/docs/tsconfig.spec.json b/tools/docs/tsconfig.spec.json index 0d3c604..88d0723 100644 --- a/tools/docs/tsconfig.spec.json +++ b/tools/docs/tsconfig.spec.json @@ -7,7 +7,7 @@ "types": ["jest", "node"] }, "include": [ - "jest.config.ts", + "jest.config.cts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts" diff --git a/tools/scripts/verify-jest-inference.mjs b/tools/scripts/verify-jest-inference.mjs new file mode 100644 index 0000000..107ade3 --- /dev/null +++ b/tools/scripts/verify-jest-inference.mjs @@ -0,0 +1,88 @@ +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const workspaceRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const graphWorkerArgument = '--read-project-graph'; + +if (process.argv.includes(graphWorkerArgument)) { + const { createProjectGraphAsync } = await import('@nx/devkit'); + const graph = await createProjectGraphAsync(); + const targets = Object.fromEntries( + [ + ['nx-forge', 'test'], + ['docs-tools', 'test'], + ['nx-forge-e2e', 'e2e'], + ].map(([projectName, targetName]) => [ + projectName, + graph.nodes[projectName]?.data.targets?.[targetName], + ]) + ); + const deprecatedTargets = Object.values(graph.nodes).flatMap(({ data }) => + Object.entries(data.targets ?? {}) + .filter(([, target]) => target.executor === '@nx/jest:jest') + .map(([targetName]) => `${data.name}:${targetName}`) + ); + + process.stdout.write(JSON.stringify({ deprecatedTargets, targets })); +} else { + const graphResult = spawnSync(process.execPath, [fileURLToPath(import.meta.url), graphWorkerArgument], { + cwd: workspaceRoot, + encoding: 'utf8', + env: { + ...process.env, + NX_CACHE_PROJECT_GRAPH: 'false', + NX_DAEMON: 'false', + }, + }); + + assert.equal( + graphResult.status, + 0, + `Nx project graph creation failed:\n${graphResult.stderr}` + ); + assert.doesNotMatch( + graphResult.stderr, + /Failed to load the ES module/, + `Jest config emitted a module-format warning:\n${graphResult.stderr}` + ); + + const { deprecatedTargets, targets } = JSON.parse(graphResult.stdout); + assert.deepEqual(deprecatedTargets, [], 'Resolved workspace targets still use @nx/jest:jest'); + + const expectedTargets = { + 'nx-forge': { + name: 'test', + cwd: 'packages/nx-forge', + output: '{workspaceRoot}/coverage/packages/nx-forge', + }, + 'docs-tools': { + name: 'test', + cwd: 'tools/docs', + output: '{workspaceRoot}/coverage/tools/docs', + }, + 'nx-forge-e2e': { + name: 'e2e', + cwd: 'e2e/nx-forge-e2e', + output: '{workspaceRoot}/coverage/e2e/nx-forge-e2e', + }, + }; + + for (const [projectName, expected] of Object.entries(expectedTargets)) { + const target = targets[projectName]; + assert.ok(target, `${projectName}:${expected.name} is not available`); + assert.equal(target.executor, 'nx:run-commands'); + assert.equal(target.cache, true); + assert.deepEqual(target.outputs, [expected.output]); + assert.equal(target.options.command, 'jest'); + assert.equal(target.options.cwd, expected.cwd); + assert.equal(target.options.passWithNoTests, true); + assert.deepEqual(target.configurations.ci, { ci: true, coverage: true }); + assert.ok(target.inputs.includes('default')); + assert.ok(target.inputs.includes('^production')); + assert.ok(target.inputs.includes('{workspaceRoot}/jest.preset.js')); + } + + process.stdout.write('Verified inferred Jest targets and warning-free config loading.\n'); +}