feat(angular): add vitest option to angular (#27311)

- This adds `vitest` option to `unitTestRunner` for Angular generators.
- This **does not** add vitest option to `create-nx-workspace` but I
think we should provide this as an option in the future. Please let me
know if you wantme to add this here or as a future feature.

---------

Co-authored-by: Colum Ferry <cferry09@gmail.com>
This commit is contained in:
Younes Jaaidi 2024-10-29 19:31:48 +01:00 committed by GitHub
parent c0c7ad7efc
commit 9fe8274367
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 481 additions and 66 deletions

View File

@ -96,8 +96,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -51,8 +51,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests of the remote if it needs to be created.", "description": "Test runner to use for unit tests of the remote if it needs to be created.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -105,8 +105,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -88,8 +88,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"importPath": { "importPath": {

View File

@ -99,8 +99,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -568,4 +568,21 @@ describe('Angular Projects', () => {
runCLI(`server ${webpackApp} --output-hashing none`) runCLI(`server ${webpackApp} --output-hashing none`)
).toThrow(); ).toThrow();
}, 500_000); }, 500_000);
it('should generate apps and libs with vitest', async () => {
const app = uniq('app');
const lib = uniq('lib');
runCLI(
`generate @nx/angular:app ${app} --unit-test-runner=vitest --no-interactive`
);
runCLI(
`generate @nx/angular:lib ${lib} --unit-test-runner=vitest --no-interactive`
);
// Make sure we are using vitest
checkFilesExist(`${app}/vite.config.mts`, `${lib}/vite.config.mts`);
runCLI(`run-many --target test --projects=${app},${lib} --parallel`);
});
}); });

View File

@ -427,6 +427,146 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`]
} }
`; `;
exports[`app --unit-test-runner vitest should add tsconfig.spec.json 1`] = `
"{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"include": [
"vite.config.ts",
"vitest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"files": ["src/test-setup.ts"]
}
"
`;
exports[`app --unit-test-runner vitest should generate src/test-setup.ts 1`] = `
"import '@analogjs/vitest-angular/setup-zone';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
"
`;
exports[`app --unit-test-runner vitest should generate vite.config.mts 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
provider: 'v8',
},
},
});
"
`;
exports[`app --unit-test-runner vitest should generate vite.config.mts if package type is module 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
provider: 'v8',
},
},
});
"
`;
exports[`app --unit-test-runner vitest should generate vite.config.mts if workspace package type is module 1`] = `
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-app',
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: ['src/test-setup.ts'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/my-app',
provider: 'v8',
},
},
});
"
`;
exports[`app angular compat support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = ` exports[`app angular compat support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';

View File

@ -1,5 +1,5 @@
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version'; import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
import type { Tree } from '@nx/devkit'; import { Tree, writeJson } from '@nx/devkit';
import * as devkit from '@nx/devkit'; import * as devkit from '@nx/devkit';
import { import {
NxJsonConfiguration, NxJsonConfiguration,
@ -698,6 +698,110 @@ describe('app', () => {
}); });
}); });
describe('vitest', () => {
it('should generate vite.config.mts', async () => {
await generateApp(appTree, 'my-app', {
skipFormat: false,
unitTestRunner: UnitTestRunner.Vitest,
});
expect(
appTree.read('my-app/vite.config.mts', 'utf-8')
).toMatchSnapshot();
});
it('should generate src/test-setup.ts', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
expect(
appTree.read('my-app/src/test-setup.ts', 'utf-8')
).toMatchSnapshot();
});
it('should exclude src/test-setup.ts in tsconfig.app.json', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
const tsConfig = readJson(appTree, 'my-app/tsconfig.app.json');
expect(tsConfig.exclude).toContain('src/test-setup.ts');
});
it('should add tsconfig.spec.json', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
expect(
appTree.read('my-app/tsconfig.spec.json', 'utf-8')
).toMatchSnapshot();
});
it('should add a reference to tsconfig.spec.json in tsconfig.json', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
const { references } = readJson(appTree, 'my-app/tsconfig.json');
expect(references).toContainEqual({
path: './tsconfig.spec.json',
});
});
it('should add @nx/vite dependency', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
const { devDependencies } = readJson(appTree, 'package.json');
expect(devDependencies['@nx/vite']).toBeDefined();
});
it('should add vitest-angular', async () => {
await generateApp(appTree, 'my-app', {
unitTestRunner: UnitTestRunner.Vitest,
});
const { devDependencies } = readJson(appTree, 'package.json');
expect(devDependencies['@analogjs/vite-plugin-angular']).toBeDefined();
expect(devDependencies['@analogjs/vitest-angular']).toBeDefined();
});
it('should generate vite.config.mts if package type is module', async () => {
writeJson(appTree, 'my-app/package.json', {
name: 'my-app',
type: 'module',
});
await generateApp(appTree, 'my-app', {
skipFormat: false,
unitTestRunner: UnitTestRunner.Vitest,
});
expect(
appTree.read('my-app/vite.config.mts', 'utf-8')
).toMatchSnapshot();
});
it('should generate vite.config.mts if workspace package type is module', async () => {
updateJson(appTree, 'package.json', (json) => ({
...json,
type: 'module',
}));
await generateApp(appTree, 'my-app', {
skipFormat: false,
unitTestRunner: UnitTestRunner.Vitest,
});
expect(
appTree.read('my-app/vite.config.mts', 'utf-8')
).toMatchSnapshot();
});
});
describe('none', () => { describe('none', () => {
it('should not generate test configuration', async () => { it('should not generate test configuration', async () => {
await generateApp(appTree, 'my-app', { await generateApp(appTree, 'my-app', {

View File

@ -1,15 +1,26 @@
import { Tree } from '@nx/devkit'; import { Tree } from '@nx/devkit';
import { UnitTestRunner } from '../../../utils/test-runners'; import { UnitTestRunner } from '../../../utils/test-runners';
import { addJest } from '../../utils/add-jest'; import { addJest } from '../../utils/add-jest';
import { addVitest } from '../../utils/add-vitest';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) { export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
if (options.unitTestRunner === UnitTestRunner.Jest) { switch (options.unitTestRunner) {
await addJest(host, { case UnitTestRunner.Jest:
name: options.name, await addJest(host, {
projectRoot: options.appProjectRoot, name: options.name,
skipPackageJson: options.skipPackageJson, projectRoot: options.appProjectRoot,
strict: options.strict, skipPackageJson: options.skipPackageJson,
}); strict: options.strict,
});
break;
case UnitTestRunner.Vitest:
await addVitest(host, {
name: options.name,
projectRoot: options.appProjectRoot,
skipPackageJson: options.skipPackageJson,
strict: options.strict,
});
break;
} }
} }

View File

@ -99,8 +99,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -51,8 +51,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests of the remote if it needs to be created.", "description": "Test runner to use for unit tests of the remote if it needs to be created.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -108,8 +108,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -30,6 +30,8 @@ import { addJest } from '../utils/add-jest';
import { setGeneratorDefaults } from './lib/set-generator-defaults'; import { setGeneratorDefaults } from './lib/set-generator-defaults';
import { ensureAngularDependencies } from '../utils/ensure-angular-dependencies'; import { ensureAngularDependencies } from '../utils/ensure-angular-dependencies';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { UnitTestRunner } from '../../utils/test-runners';
import { addVitest } from '../utils/add-vitest';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function libraryGenerator( export async function libraryGenerator(
@ -130,13 +132,23 @@ async function addUnitTestRunner(
host: Tree, host: Tree,
options: NormalizedSchema['libraryOptions'] options: NormalizedSchema['libraryOptions']
) { ) {
if (options.unitTestRunner === 'jest') { switch (options.unitTestRunner) {
await addJest(host, { case UnitTestRunner.Jest:
name: options.name, await addJest(host, {
projectRoot: options.projectRoot, name: options.name,
skipPackageJson: options.skipPackageJson, projectRoot: options.projectRoot,
strict: options.strict, skipPackageJson: options.skipPackageJson,
}); strict: options.strict,
});
break;
case UnitTestRunner.Vitest:
await addVitest(host, {
name: options.name,
projectRoot: options.projectRoot,
skipPackageJson: options.skipPackageJson,
strict: options.strict,
});
break;
} }
} }

View File

@ -88,8 +88,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"importPath": { "importPath": {

View File

@ -102,8 +102,9 @@
}, },
"unitTestRunner": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "vitest", "none"],
"description": "Test runner to use for unit tests.", "description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "jest" "default": "jest"
}, },
"e2eTestRunner": { "e2eTestRunner": {

View File

@ -0,0 +1,84 @@
import {
addDependenciesToPackageJson,
ensurePackage,
joinPathFragments,
type Tree,
} from '@nx/devkit';
import { analogVitestAngular, nxVersion } from '../../utils/versions';
export type AddVitestOptions = {
name: string;
projectRoot: string;
skipPackageJson: boolean;
strict: boolean;
};
export async function addVitest(
tree: Tree,
options: AddVitestOptions
): Promise<void> {
if (!options.skipPackageJson) {
addDependenciesToPackageJson(
tree,
{},
{
'@analogjs/vitest-angular': analogVitestAngular,
'@analogjs/vite-plugin-angular': analogVitestAngular,
},
undefined,
true
);
}
const { createOrEditViteConfig, viteConfigurationGenerator } = ensurePackage<
typeof import('@nx/vite')
>('@nx/vite', nxVersion);
const relativeTestSetupPath = joinPathFragments('src', 'test-setup.ts');
const setupFile = joinPathFragments(
options.projectRoot,
relativeTestSetupPath
);
if (!tree.exists(setupFile)) {
tree.write(
setupFile,
`import '@analogjs/vitest-angular/setup-zone';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import { getTestBed } from '@angular/core/testing';
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
`
);
await viteConfigurationGenerator(tree, {
project: options.name,
newProject: true,
uiFramework: 'none',
includeVitest: true,
testEnvironment: 'jsdom',
});
createOrEditViteConfig(
tree,
{
project: options.name,
includeLib: false,
includeVitest: true,
inSourceTests: false,
imports: [`import angular from '@analogjs/vite-plugin-angular'`],
plugins: ['angular()'],
setupFile: relativeTestSetupPath,
useEsmExtension: true,
},
true
);
}
}

View File

@ -17,13 +17,16 @@ export type PackageVersionNames =
export type VersionMap = { export type VersionMap = {
angularV16: Record< angularV16: Record<
Exclude<CompatPackageVersionNames, 'typescriptEslintVersion'>, Exclude<
CompatPackageVersionNames,
'analogVitestAngular' | 'typescriptEslintVersion'
>,
string string
>; >;
angularV17: Record< angularV17: Record<
Exclude< Exclude<
CompatPackageVersionNames, CompatPackageVersionNames,
'ngUniversalVersion' | 'typescriptEslintVersion' 'analogVitestAngular' | 'ngUniversalVersion' | 'typescriptEslintVersion'
>, >,
string string
>; >;

View File

@ -1,6 +1,7 @@
export enum UnitTestRunner { export enum UnitTestRunner {
Jest = 'jest', Jest = 'jest',
None = 'none', None = 'none',
Vitest = 'vitest',
} }
export enum E2eTestRunner { export enum E2eTestRunner {

View File

@ -28,5 +28,6 @@ export const tsNodeVersion = '10.9.1';
export const jestPresetAngularVersion = '~14.1.0'; export const jestPresetAngularVersion = '~14.1.0';
export const typesNodeVersion = '18.16.9'; export const typesNodeVersion = '18.16.9';
export const jasmineMarblesVersion = '^0.9.2'; export const jasmineMarblesVersion = '^0.9.2';
export const analogVitestAngular = '~1.9.1';
export const jsoncEslintParserVersion = '^2.1.0'; export const jsoncEslintParserVersion = '^2.1.0';

View File

@ -214,11 +214,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-app', cacheDir: '../node_modules/.vite/my-app',
server:{ server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview:{ preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
@ -244,7 +244,7 @@ export default defineConfig({
coverage: { coverage: {
reportsDirectory: '../coverage/my-app', reportsDirectory: '../coverage/my-app',
provider: 'v8', provider: 'v8',
} },
}, },
}); });
" "
@ -278,11 +278,11 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-app', cacheDir: '../node_modules/.vite/my-app',
server:{ server: {
port: 4200, port: 4200,
host: 'localhost', host: 'localhost',
}, },
preview:{ preview: {
port: 4300, port: 4300,
host: 'localhost', host: 'localhost',
}, },
@ -308,7 +308,7 @@ export default defineConfig({
coverage: { coverage: {
reportsDirectory: '../coverage/my-app', reportsDirectory: '../coverage/my-app',
provider: 'v8', provider: 'v8',
} },
}, },
}); });
" "

View File

@ -52,6 +52,7 @@ describe('app', () => {
it('should add vite types to tsconfigs', async () => { it('should add vite types to tsconfigs', async () => {
await applicationGenerator(appTree, { await applicationGenerator(appTree, {
...schema, ...schema,
skipFormat: false,
bundler: 'vite', bundler: 'vite',
unitTestRunner: 'vitest', unitTestRunner: 'vitest',
}); });
@ -198,6 +199,7 @@ describe('app', () => {
it('should use preview vite types to tsconfigs', async () => { it('should use preview vite types to tsconfigs', async () => {
await applicationGenerator(appTree, { await applicationGenerator(appTree, {
...schema, ...schema,
skipFormat: false,
bundler: 'vite', bundler: 'vite',
unitTestRunner: 'vitest', unitTestRunner: 'vitest',
}); });

View File

@ -39,7 +39,15 @@ import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
cacheDir: '../node_modules/.vite/my-lib', cacheDir: '../node_modules/.vite/my-lib',
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })], plugins: [
react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
dts({
entryRoot: 'src',
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers. // Uncomment this if you are using workers.
// worker: { // worker: {
// plugins: [ nxViteTsPaths() ], // plugins: [ nxViteTsPaths() ],
@ -60,11 +68,11 @@ export default defineConfig({
fileName: 'index', fileName: 'index',
// Change this to the formats you want to support. // Change this to the formats you want to support.
// Don't forget to update your package.json as well. // Don't forget to update your package.json as well.
formats: ['es', 'cjs'] formats: ['es', 'cjs'],
}, },
rollupOptions: { rollupOptions: {
// External packages that should not be bundled into your library. // External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime'] external: ['react', 'react-dom', 'react/jsx-runtime'],
}, },
}, },
test: { test: {
@ -76,7 +84,7 @@ export default defineConfig({
coverage: { coverage: {
reportsDirectory: '../coverage/my-lib', reportsDirectory: '../coverage/my-lib',
provider: 'v8', provider: 'v8',
} },
}, },
}); });
" "

View File

@ -70,6 +70,7 @@ describe('lib', () => {
it('should add vite types to tsconfigs', async () => { it('should add vite types to tsconfigs', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
...defaultSchema, ...defaultSchema,
skipFormat: false,
bundler: 'vite', bundler: 'vite',
unitTestRunner: 'vitest', unitTestRunner: 'vitest',
}); });

View File

@ -73,6 +73,7 @@ describe('@nx/vite:init', () => {
"default", "default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json", "!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/src/test-setup.[jt]s",
], ],
}, },
"plugins": [ "plugins": [

View File

@ -22,7 +22,8 @@ export function updateNxJsonSettings(tree: Tree) {
if (productionFileSet) { if (productionFileSet) {
productionFileSet.push( productionFileSet.push(
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)', '!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
'!{projectRoot}/tsconfig.spec.json' '!{projectRoot}/tsconfig.spec.json',
'!{projectRoot}/src/test-setup.[jt]s'
); );
nxJson.namedInputs.production = Array.from(new Set(productionFileSet)); nxJson.namedInputs.production = Array.from(new Set(productionFileSet));

View File

@ -155,6 +155,8 @@ function updateTsConfig(
projectRoot: string, projectRoot: string,
projectType: ProjectType projectType: ProjectType
) { ) {
const setupFile = tryFindSetupFile(tree, projectRoot);
if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.spec.json'))) { if (tree.exists(joinPathFragments(projectRoot, 'tsconfig.spec.json'))) {
updateJson( updateJson(
tree, tree,
@ -168,6 +170,11 @@ function updateTsConfig(
json.compilerOptions.types = ['vitest']; json.compilerOptions.types = ['vitest'];
} }
} }
if (setupFile) {
json.files = [...(json.files ?? []), setupFile];
}
return json; return json;
} }
); );
@ -221,18 +228,11 @@ function updateTsConfig(
} }
} }
if (options.inSourceTests) { if (tree.exists(runtimeTsconfigPath)) {
if (tree.exists(runtimeTsconfigPath)) { updateJson(tree, runtimeTsconfigPath, (json) => {
updateJson(tree, runtimeTsconfigPath, (json) => { if (options.inSourceTests) {
(json.compilerOptions.types ??= []).push('vitest/importMeta'); (json.compilerOptions.types ??= []).push('vitest/importMeta');
return json; } else {
});
}
addTsLibDependencies(tree);
} else {
if (tree.exists(runtimeTsconfigPath)) {
updateJson(tree, runtimeTsconfigPath, (json) => {
const uniqueExclude = new Set([ const uniqueExclude = new Set([
...(json.exclude || []), ...(json.exclude || []),
'vite.config.ts', 'vite.config.ts',
@ -247,14 +247,19 @@ function updateTsConfig(
'src/**/*.spec.jsx', 'src/**/*.spec.jsx',
]); ]);
json.exclude = [...uniqueExclude]; json.exclude = [...uniqueExclude];
return json; }
});
} else { if (setupFile) {
logger.warn( json.exclude = [...(json.exclude ?? []), setupFile];
`Couldn't find a runtime tsconfig file at ${runtimeTsconfigPath} to exclude the test files from. ` + }
`If you're using a different filename for your runtime tsconfig, please provide it with the '--runtimeTsconfigFileName' flag.`
); return json;
} });
} else {
logger.warn(
`Couldn't find a runtime tsconfig file at ${runtimeTsconfigPath} to exclude the test files from. ` +
`If you're using a different filename for your runtime tsconfig, please provide it with the '--runtimeTsconfigFileName' flag.`
);
} }
} }
@ -290,4 +295,11 @@ function getCoverageProviderDependency(
} }
} }
function tryFindSetupFile(tree: Tree, projectRoot: string) {
const setupFile = joinPathFragments('src', 'test-setup.ts');
if (tree.exists(joinPathFragments(projectRoot, setupFile))) {
return setupFile;
}
}
export default vitestGenerator; export default vitestGenerator;

View File

@ -362,6 +362,8 @@ export interface ViteConfigFileOptions {
imports?: string[]; imports?: string[];
plugins?: string[]; plugins?: string[];
coverageProvider?: 'v8' | 'istanbul' | 'custom'; coverageProvider?: 'v8' | 'istanbul' | 'custom';
setupFile?: string;
useEsmExtension?: boolean;
} }
export function createOrEditViteConfig( export function createOrEditViteConfig(
@ -371,11 +373,15 @@ export function createOrEditViteConfig(
projectAlreadyHasViteTargets?: TargetFlags, projectAlreadyHasViteTargets?: TargetFlags,
vitestFileName?: boolean vitestFileName?: boolean
) { ) {
const { root: projectRoot } = readProjectConfiguration(tree, options.project); const { root: projectRoot, projectType } = readProjectConfiguration(
tree,
options.project
);
const extension = options.useEsmExtension ? 'mts' : 'ts';
const viteConfigPath = vitestFileName const viteConfigPath = vitestFileName
? `${projectRoot}/vitest.config.ts` ? `${projectRoot}/vitest.config.${extension}`
: `${projectRoot}/vite.config.ts`; : `${projectRoot}/vite.config.${extension}`;
const isUsingTsPlugin = isUsingTsSolutionSetup(tree); const isUsingTsPlugin = isUsingTsSolutionSetup(tree);
const buildOutDir = isUsingTsPlugin const buildOutDir = isUsingTsPlugin
@ -453,12 +459,13 @@ export function createOrEditViteConfig(
watch: false, watch: false,
globals: true, globals: true,
environment: '${options.testEnvironment ?? 'jsdom'}', environment: '${options.testEnvironment ?? 'jsdom'}',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],${ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
options.inSourceTests ${options.setupFile ? ` setupFiles: ['${options.setupFile}'],\n` : ''}\
? ` ${
includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],` options.inSourceTests
: '' ? ` includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],\n`
} : ''
}\
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '${reportsDirectory}', reportsDirectory: '${reportsDirectory}',