fix(angular): ensure crystal targets for testing when bundler=rspack (#30631)

## Current Behavior
Angular Rspack relies on Inferred Targets however, when scaffolding the
application, the unit test runners are being set up with executors.

## Expected Behavior
Ensure that when `bundler=rspack` unit test runners are being set up
with inference plugins
This commit is contained in:
Colum Ferry 2025-04-10 11:06:15 +01:00 committed by GitHub
parent d4ebf82ac8
commit c71a7832b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 66 additions and 11 deletions

View File

@ -40,7 +40,7 @@ export async function addLintingGenerator(
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
skipFormat: true, skipFormat: true,
rootProject: rootProject, rootProject: rootProject,
addPlugin: false, addPlugin: options.addPlugin ?? false,
addExplicitTargets: true, addExplicitTargets: true,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
}); });

View File

@ -6,4 +6,5 @@ export interface AddLintingGeneratorSchema {
skipFormat?: boolean; skipFormat?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
unitTestRunner?: string; unitTestRunner?: string;
addPlugin?: boolean;
} }

View File

@ -1246,6 +1246,7 @@ describe('app', () => {
] ]
`); `);
}); });
it('should generate a correct setup when --bundler=rspack including a correct config file and no build target', async () => { it('should generate a correct setup when --bundler=rspack including a correct config file and no build target', async () => {
await generateApp(appTree, 'app1', { await generateApp(appTree, 'app1', {
bundler: 'rspack', bundler: 'rspack',
@ -1257,6 +1258,42 @@ describe('app', () => {
expect(appTree.read('app1/rspack.config.ts', 'utf-8')).toMatchSnapshot(); expect(appTree.read('app1/rspack.config.ts', 'utf-8')).toMatchSnapshot();
}); });
it('should generate use crystal jest when --bundler=rspack', async () => {
await generateApp(appTree, 'app1', {
bundler: 'rspack',
unitTestRunner: UnitTestRunner.Jest,
});
const project = readProjectConfiguration(appTree, 'app1');
expect(project.targets.test).not.toBeDefined();
const nxJson = readNxJson(appTree);
const jestPlugin = nxJson.plugins.find(
(p) =>
(typeof p === 'string' && p === '@nx/jest/plugin') ||
(typeof p !== 'string' && p.plugin === '@nx/jest/plugin')
);
expect(jestPlugin).toBeDefined();
});
it('should generate use crystal vitest when --bundler=rspack', async () => {
await generateApp(appTree, 'app1', {
bundler: 'rspack',
unitTestRunner: UnitTestRunner.Vitest,
});
const project = readProjectConfiguration(appTree, 'app1');
expect(project.targets.test).not.toBeDefined();
const nxJson = readNxJson(appTree);
const vitePlugin = nxJson.plugins.find(
(p) =>
(typeof p === 'string' && p === '@nx/vite/plugin') ||
(typeof p !== 'string' && p.plugin === '@nx/vite/plugin')
);
expect(vitePlugin).toBeDefined();
});
it('should generate target options "browser" and "buildTarget"', async () => { it('should generate target options "browser" and "buildTarget"', async () => {
await generateApp(appTree, 'my-app', { standalone: true }); await generateApp(appTree, 'my-app', { standalone: true });

View File

@ -31,7 +31,6 @@ import {
updateEditorTsConfig, updateEditorTsConfig,
} from './lib'; } from './lib';
import type { Schema } from './schema'; import type { Schema } from './schema';
import { tsNodeVersion } from '../../utils/versions';
export async function applicationGenerator( export async function applicationGenerator(
tree: Tree, tree: Tree,
@ -43,7 +42,7 @@ export async function applicationGenerator(
schema.bundler = 'webpack'; schema.bundler = 'webpack';
} }
const options = await normalizeOptions(tree, schema); const options = await normalizeOptions(tree, schema, isRspack);
const rootOffset = offsetFromRoot(options.appProjectRoot); const rootOffset = offsetFromRoot(options.appProjectRoot);
await jsInitGenerator(tree, { await jsInitGenerator(tree, {
@ -55,6 +54,7 @@ export async function applicationGenerator(
await angularInitGenerator(tree, { await angularInitGenerator(tree, {
...options, ...options,
skipFormat: true, skipFormat: true,
addPlugin: options.addPlugin,
}); });
if (!options.skipPackageJson) { if (!options.skipPackageJson) {

View File

@ -15,8 +15,8 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
// since e2e are separate projects, default to adding plugins // since e2e are separate projects, default to adding plugins
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
const addPlugin = const addPlugin =
process.env.NX_ADD_PLUGINS !== 'false' && nxJson['useInferencePlugins'] !== false &&
nxJson.useInferencePlugins !== false; process.env.NX_ADD_PLUGINS !== 'false';
const e2eWebServerInfo = getAngularE2EWebServerInfo( const e2eWebServerInfo = getAngularE2EWebServerInfo(
tree, tree,

View File

@ -16,5 +16,6 @@ export async function addLinting(host: Tree, options: NormalizedSchema) {
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
unitTestRunner: options.unitTestRunner, unitTestRunner: options.unitTestRunner,
skipFormat: true, skipFormat: true,
addPlugin: options.addPlugin,
}); });
} }

View File

@ -12,6 +12,7 @@ export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
projectRoot: options.appProjectRoot, projectRoot: options.appProjectRoot,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
strict: options.strict, strict: options.strict,
addPlugin: options.addPlugin,
}); });
break; break;
case UnitTestRunner.Vitest: case UnitTestRunner.Vitest:
@ -20,6 +21,7 @@ export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
projectRoot: options.appProjectRoot, projectRoot: options.appProjectRoot,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
strict: options.strict, strict: options.strict,
addPlugin: options.addPlugin,
}); });
break; break;
} }

View File

@ -1,4 +1,4 @@
import { joinPathFragments, type Tree } from '@nx/devkit'; import { joinPathFragments, readNxJson, type Tree } from '@nx/devkit';
import { import {
determineProjectNameAndRootOptions, determineProjectNameAndRootOptions,
ensureRootProjectName, ensureRootProjectName,
@ -8,9 +8,16 @@ import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
function arePluginsExplicitlyDisabled(host: Tree) {
const { useInferencePlugins } = readNxJson(host);
const addPluginEnvVar = process.env.NX_ADD_PLUGINS;
return useInferencePlugins === false || addPluginEnvVar === 'false';
}
export async function normalizeOptions( export async function normalizeOptions(
host: Tree, host: Tree,
options: Partial<Schema> options: Partial<Schema>,
isRspack?: boolean
): Promise<NormalizedSchema> { ): Promise<NormalizedSchema> {
await ensureRootProjectName(options as Schema, 'application'); await ensureRootProjectName(options as Schema, 'application');
const { projectName: appProjectName, projectRoot: appProjectRoot } = const { projectName: appProjectName, projectRoot: appProjectRoot } =
@ -31,8 +38,12 @@ export async function normalizeOptions(
const bundler = options.bundler ?? 'esbuild'; const bundler = options.bundler ?? 'esbuild';
const addPlugin =
options.addPlugin ?? (!arePluginsExplicitlyDisabled(host) && isRspack);
// Set defaults and then overwrite with user options // Set defaults and then overwrite with user options
return { return {
addPlugin,
style: 'css', style: 'css',
routing: true, routing: true,
inlineStyle: false, inlineStyle: false,

View File

@ -31,4 +31,5 @@ export interface Schema {
ssr?: boolean; ssr?: boolean;
serverRouting?: boolean; serverRouting?: boolean;
nxCloudToken?: string; nxCloudToken?: string;
addPlugin?: boolean;
} }

View File

@ -410,8 +410,8 @@ export async function convertToRspack(
); );
} }
} }
serveTargetNames.push(targetName);
} }
serveTargetNames.push(targetName);
} }
const customWebpackConfigInfo = customWebpackConfigPath const customWebpackConfigInfo = customWebpackConfigPath

View File

@ -11,6 +11,7 @@ export type AddJestOptions = {
projectRoot: string; projectRoot: string;
skipPackageJson: boolean; skipPackageJson: boolean;
strict: boolean; strict: boolean;
addPlugin?: boolean;
}; };
export async function addJest( export async function addJest(
@ -40,8 +41,8 @@ export async function addJest(
skipSerializers: false, skipSerializers: false,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
skipFormat: true, skipFormat: true,
addPlugin: false, addPlugin: options.addPlugin ?? false,
addExplicitTargets: true, addExplicitTargets: !options.addPlugin,
}); });
const setupFile = joinPathFragments( const setupFile = joinPathFragments(

View File

@ -6,6 +6,7 @@ export type AddVitestOptions = {
projectRoot: string; projectRoot: string;
skipPackageJson: boolean; skipPackageJson: boolean;
strict: boolean; strict: boolean;
addPlugin?: boolean;
}; };
export async function addVitest( export async function addVitest(
@ -22,6 +23,6 @@ export async function addVitest(
uiFramework: 'angular', uiFramework: 'angular',
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
coverageProvider: 'v8', coverageProvider: 'v8',
addPlugin: false, addPlugin: options.addPlugin ?? false,
}); });
} }