From df1d13080744852c0b15a22bc1def33f184e35f6 Mon Sep 17 00:00:00 2001 From: Tine Kondo Date: Mon, 16 Dec 2024 11:08:53 +0000 Subject: [PATCH] feat(core): add option `ciGroupName` to allow customizing the name of `Jest` atomized taks group on CI Closes #28066 --- .../packages/jest/documents/overview.md | 23 + docs/shared/packages/jest/jest-plugin.md | 23 + packages/jest/src/plugins/plugin.spec.ts | 887 +++++++++++++----- packages/jest/src/plugins/plugin.ts | 31 +- 4 files changed, 740 insertions(+), 224 deletions(-) diff --git a/docs/generated/packages/jest/documents/overview.md b/docs/generated/packages/jest/documents/overview.md index 92f5399ef0433..056b3045bfa71 100644 --- a/docs/generated/packages/jest/documents/overview.md +++ b/docs/generated/packages/jest/documents/overview.md @@ -91,6 +91,29 @@ target with that name which can be used in CI to run the tests for each file in } ``` +### Customizing atomized unit/e2e tasks group name + +By default, atomized tasks group name is derived from the `ciTargetName`. For example, atomized tasks for `e2e-ci` target, will be grouped under the name "E2E (CI) when displayed in Nx Cloud or `nx show project --web` UI. +You can customize that name, by explicitly providing the optional `ciGroupName` in the plugin option, as such: + +```json {% fileName="nx.json" %} +{ + "plugins": [ + { + "plugin": "@nx/jest/plugin", + "include": ["e2e/**/*"], + "options": { + "targetName": "e2e-local", + "ciTargetName": "e2e-ci" + "ciGroupname": "My E2E tests (CI) + } + } + ] +} +``` + +if you don't provide that option, the plugin will try to deduct the group name as described above, and will fallback to "E2E (CI)" if it can't. + ### How @nx/jest Infers Tasks {% callout type="note" title="Inferred Tasks" %} diff --git a/docs/shared/packages/jest/jest-plugin.md b/docs/shared/packages/jest/jest-plugin.md index 92f5399ef0433..056b3045bfa71 100644 --- a/docs/shared/packages/jest/jest-plugin.md +++ b/docs/shared/packages/jest/jest-plugin.md @@ -91,6 +91,29 @@ target with that name which can be used in CI to run the tests for each file in } ``` +### Customizing atomized unit/e2e tasks group name + +By default, atomized tasks group name is derived from the `ciTargetName`. For example, atomized tasks for `e2e-ci` target, will be grouped under the name "E2E (CI) when displayed in Nx Cloud or `nx show project --web` UI. +You can customize that name, by explicitly providing the optional `ciGroupName` in the plugin option, as such: + +```json {% fileName="nx.json" %} +{ + "plugins": [ + { + "plugin": "@nx/jest/plugin", + "include": ["e2e/**/*"], + "options": { + "targetName": "e2e-local", + "ciTargetName": "e2e-ci" + "ciGroupname": "My E2E tests (CI) + } + } + ] +} +``` + +if you don't provide that option, the plugin will try to deduct the group name as described above, and will fallback to "E2E (CI)" if it can't. + ### How @nx/jest Infers Tasks {% callout type="note" title="Inferred Tasks" %} diff --git a/packages/jest/src/plugins/plugin.spec.ts b/packages/jest/src/plugins/plugin.spec.ts index 3cf4ae505883f..323a29511f749 100644 --- a/packages/jest/src/plugins/plugin.spec.ts +++ b/packages/jest/src/plugins/plugin.spec.ts @@ -140,7 +140,7 @@ describe('@nx/jest/plugin', () => { "proj": { "metadata": { "targetGroups": { - "E2E (CI)": [ + "TEST (CI)": [ "test-ci", "test-ci--src/unit.spec.ts", ], @@ -378,135 +378,135 @@ describe('@nx/jest/plugin', () => { ); expect(results).toMatchInlineSnapshot(` - [ - [ - "proj/jest.config.js", - { - "projects": { - "proj": { - "metadata": { - "targetGroups": { - "E2E (CI)": [ - "test-ci", - "test-ci--src/unit.spec.ts", - ], - }, - }, - "root": "proj", - "targets": { - "test": { - "cache": true, - "command": "jest", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], - }, - ], - "metadata": { - "description": "Run Jest Tests", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": { + "targetGroups": { + "TEST (CI)": [ + "test-ci", + "test-ci--src/unit.spec.ts", + ], }, }, - }, - "technologies": [ - "jest", - ], - }, - "options": { - "cwd": "proj", - "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", - }, - }, - "outputs": [ - "{workspaceRoot}/coverage", - ], - }, - "test-ci": { - "cache": true, - "dependsOn": [ - "test-ci--src/unit.spec.ts", - ], - "executor": "nx:noop", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], - }, - ], - "metadata": { - "description": "Run Jest Tests in CI", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + }, + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, - }, - }, - "nonAtomizedTarget": "test", - "technologies": [ - "jest", - ], - }, - "outputs": [ - "{workspaceRoot}/coverage", - ], - }, - "test-ci--src/unit.spec.ts": { - "cache": true, - "command": "jest src/unit.spec.ts", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], - }, - ], - "metadata": { - "description": "Run Jest Tests in src/unit.spec.ts", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, + "test-ci": { + "cache": true, + "dependsOn": [ + "test-ci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "nonAtomizedTarget": "test", + "technologies": [ + "jest", + ], + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + }, + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, }, }, - "technologies": [ - "jest", - ], - }, - "options": { - "cwd": "proj", - "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", - }, }, - "outputs": [ - "{workspaceRoot}/coverage", - ], }, - }, - }, - }, - }, - ], - ] - `); + ], + ] + `); }); it.each` @@ -564,137 +564,578 @@ describe('@nx/jest/plugin', () => { ); expect(results).toMatchInlineSnapshot(` - [ + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": { + "targetGroups": { + "TEST (CI)": [ + "test-ci", + "test-ci--src/unit.spec.ts", + ], + }, + }, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + }, + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci": { + "cache": true, + "dependsOn": [ + "test-ci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "nonAtomizedTarget": "test", + "technologies": [ + "jest", + ], + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + }, + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + }, + }, + }, + }, + ], + ] + `); + } + ); + }); + + describe('ciGroupName', () => { + it('should name atomized tasks group using provided group name', async () => { + mockJestConfig( + { + coverageDirectory: '../coverage', + testMatch: ['**/*.spec.ts'], + testPathIgnorePatterns: ['ignore.spec.ts'], + }, + context + ); + const results = await createNodesFunction( + ['proj/jest.config.js'], + { + ciTargetName: 'test-ci', + ciGroupName: 'MY ATOMIZED TEST TASKS (CI)', + }, + context + ); + + expect(results).toMatchInlineSnapshot(` [ - "proj/jest.config.js", - { - "projects": { - "proj": { - "metadata": { - "targetGroups": { - "E2E (CI)": [ - "test-ci", - "test-ci--src/unit.spec.ts", - ], + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": { + "targetGroups": { + "MY ATOMIZED TEST TASKS (CI)": [ + "test-ci", + "test-ci--src/unit.spec.ts", + ], + }, }, - }, - "root": "proj", - "targets": { - "test": { - "cache": true, - "command": "jest", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ "jest", ], }, - ], - "metadata": { - "description": "Run Jest Tests", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, - }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", }, }, - "technologies": [ - "jest", + "outputs": [ + "{workspaceRoot}/coverage", ], }, - "options": { - "cwd": "proj", - "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "test-ci": { + "cache": true, + "dependsOn": [ + "test-ci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "nonAtomizedTarget": "test", + "technologies": [ + "jest", + ], }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, - "outputs": [ - "{workspaceRoot}/coverage", - ], - }, - "test-ci": { - "cache": true, - "dependsOn": [ - "test-ci--src/unit.spec.ts", - ], - "executor": "nx:noop", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ + "test-ci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ "jest", ], }, - ], + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + }, + }, + }, + }, + ], + ] + `); + }); + + it('should deduct atomized tasks group name (from ci target name) when not explicitly provided', async () => { + mockJestConfig( + { + coverageDirectory: '../coverage', + testMatch: ['**/*.spec.ts'], + testPathIgnorePatterns: ['ignore.spec.ts'], + }, + context + ); + const results = await createNodesFunction( + ['proj/jest.config.js'], + { + ciTargetName: 'test-ci', + }, + context + ); + + expect(results).toMatchInlineSnapshot(` + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { "metadata": { - "description": "Run Jest Tests in CI", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, + "targetGroups": { + "TEST (CI)": [ + "test-ci", + "test-ci--src/unit.spec.ts", + ], + }, + }, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", }, }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci": { + "cache": true, + "dependsOn": [ + "test-ci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "nonAtomizedTarget": "test", + "technologies": [ + "jest", + ], + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, - "nonAtomizedTarget": "test", - "technologies": [ - "jest", - ], }, - "outputs": [ - "{workspaceRoot}/coverage", - ], }, - "test-ci--src/unit.spec.ts": { - "cache": true, - "command": "jest src/unit.spec.ts", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", + }, + }, + ], + ] + `); + }); + + it('should default to "E2E (CI)" when group name cannot be automatically deducted from ci target name', async () => { + mockJestConfig( + { + coverageDirectory: '../coverage', + testMatch: ['**/*.spec.ts'], + testPathIgnorePatterns: ['ignore.spec.ts'], + }, + context + ); + const results = await createNodesFunction( + ['proj/jest.config.js'], + { + ciTargetName: 'testci', // missing "-ci" suffix or similar construct, so group name cannot reliably be deducted from ci target name + }, + context + ); + + expect(results).toMatchInlineSnapshot(` + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": { + "targetGroups": { + "E2E (CI)": [ + "testci", + "testci--src/unit.spec.ts", ], }, - ], - "metadata": { - "description": "Run Jest Tests in src/unit.spec.ts", - "help": { - "command": "npx jest --help", - "example": { - "options": { - "coverage": true, + }, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + "env": { + "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", }, }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, - "technologies": [ - "jest", - ], - }, - "options": { - "cwd": "proj", - "env": { - "TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}", + "testci": { + "cache": true, + "dependsOn": [ + "testci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "nonAtomizedTarget": "test", + "technologies": [ + "jest", + ], + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "testci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "help": { + "command": "npx jest --help", + "example": { + "options": { + "coverage": true, + }, + }, + }, + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], }, }, - "outputs": [ - "{workspaceRoot}/coverage", - ], }, }, }, - }, - }, - ], - ] - `); - } - ); + ], + ] + `); + }); }); }); diff --git a/packages/jest/src/plugins/plugin.ts b/packages/jest/src/plugins/plugin.ts index b761c25233203..f5e561a7cbda0 100644 --- a/packages/jest/src/plugins/plugin.ts +++ b/packages/jest/src/plugins/plugin.ts @@ -38,6 +38,11 @@ const pmc = getPackageManagerCommand(); export interface JestPluginOptions { targetName?: string; ciTargetName?: string; + + /** + * The name that should be used to group atomized tasks on CI + */ + ciGroupName?: string; /** * Whether to use jest-config and jest-runtime to load Jest configuration and context. * Disabling this is much faster but could be less correct since we are using our own config loader @@ -221,7 +226,8 @@ async function buildJestTargets( let metadata: ProjectConfiguration['metadata']; - const groupName = 'E2E (CI)'; + const groupName = + options?.ciGroupName ?? deductGroupNameFromTarget(options?.ciTargetName); if (options.disableJestRuntime) { const outputs = (target.outputs = getOutputs( @@ -687,3 +693,26 @@ async function getJestOption( return undefined; } + +/** + * Helper that tries to deduct the name of the CI group, based on the related target name. + * + * This will work well, when the ci target name follows the documented naming convention or similar (for e.g `test-ci`, `e2e-ci`, `ny-e2e-ci`, etc). + * + * For example, `test-ci` => `TEST (CI)`, `e2e-ci` => `E2E (CI)`, `my-e2e-ci` => `MY E2E (CI)` + * + * + * @param ciTargetName name of the ci target + * @returns the deducted group name or `E2E (CI)` if cannot be deducted automatically + */ +function deductGroupNameFromTarget(ciTargetName: string | undefined) { + if (ciTargetName) { + const parts = ciTargetName.split('-').map((v) => v.toUpperCase()); + + if (parts.length > 1) { + return `${parts.slice(0, -1).join(' ')} (${parts[parts.length - 1]})`; + } + } + + return 'E2E (CI)'; // default group name when cannot be deducted from target name +}