feat(testing): add vitest generators (#13301)
This commit is contained in:
parent
17514d2366
commit
02e22de7ed
@ -162,10 +162,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files. Read more on the Vitest docs site: https://vitest.dev/guide/in-source.html"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["cypress", "none"],
|
||||
@ -332,10 +337,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the library (used for linting).",
|
||||
|
||||
@ -81,6 +81,48 @@
|
||||
"hidden": false,
|
||||
"implementation": "/packages/vite/src/generators/configuration/configuration.ts",
|
||||
"path": "/packages/vite/src/generators/configuration/schema.json"
|
||||
},
|
||||
{
|
||||
"name": "vitest",
|
||||
"factory": "./src/generators/vitest/vitest-generator",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"cli": "nx",
|
||||
"$id": "Vitest",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"description": "Generate a vitest setup for a project.",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project to test.",
|
||||
"$default": { "$source": "projectName" }
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"enum": ["react", "none"],
|
||||
"default": "none",
|
||||
"description": "UI framework to use with vitest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not generate separate spec files and set up in-source testing"
|
||||
},
|
||||
"skipViteConfig": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Skip generating a vite config file"
|
||||
}
|
||||
},
|
||||
"required": ["project"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Generate a vitest configuration",
|
||||
"implementation": "/packages/vite/src/generators/vitest/vitest-generator.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/vite/src/generators/vitest/schema.json"
|
||||
}
|
||||
],
|
||||
"executors": [
|
||||
|
||||
@ -136,10 +136,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files."
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["cypress", "none"],
|
||||
|
||||
@ -372,7 +372,7 @@
|
||||
"path": "generated/packages/vite.json",
|
||||
"schemas": {
|
||||
"executors": ["dev-server", "build", "test"],
|
||||
"generators": ["init", "configuration"]
|
||||
"generators": ["init", "configuration", "vitest"]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
createFile,
|
||||
exists,
|
||||
killPorts,
|
||||
listFiles,
|
||||
newProject,
|
||||
@ -10,6 +11,7 @@ import {
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommandUntil,
|
||||
tmpProjPath,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateProjectConfig,
|
||||
@ -391,4 +393,45 @@ describe('Vite Plugin', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be able to create libs that use vitest', () => {
|
||||
const lib = uniq('my-lib');
|
||||
beforeEach(() => {
|
||||
proj = newProject();
|
||||
});
|
||||
|
||||
it('should be able to run tests', async () => {
|
||||
runCLI(`generate @nrwl/react:lib ${lib} --unitTestRunner=vitest`);
|
||||
expect(exists(tmpProjPath(`libs/${lib}/vite.config.ts`))).toBeTruthy();
|
||||
|
||||
const result = await runCLIAsync(`test ${lib}`);
|
||||
expect(result.combinedOutput).toContain(
|
||||
`Successfully ran target test for project ${lib}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to run tests with inSourceTests set to true', async () => {
|
||||
runCLI(
|
||||
`generate @nrwl/react:lib ${lib} --unitTestRunner=vitest --inSourceTests`
|
||||
);
|
||||
expect(
|
||||
exists(tmpProjPath(`libs/${lib}/src/lib/${lib}.spec.tsx`))
|
||||
).toBeFalsy();
|
||||
|
||||
updateFile(`libs/${lib}/src/lib/${lib}.tsx`, (content) => {
|
||||
content += `
|
||||
if (import.meta.vitest) {
|
||||
const { expect, it } = import.meta.vitest;
|
||||
it('should be successful', () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
}
|
||||
`;
|
||||
return content;
|
||||
});
|
||||
|
||||
const result = await runCLIAsync(`test ${lib}`);
|
||||
expect(result.combinedOutput).toContain(`1 passed`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,9 +29,7 @@ describe('Web Components Applications with bundler set as vite', () => {
|
||||
|
||||
const testResults = await runCLIAsync(`test ${appName}`);
|
||||
|
||||
expect(testResults.combinedOutput).toContain(
|
||||
'Test Suites: 1 passed, 1 total'
|
||||
);
|
||||
expect(testResults.combinedOutput).toContain('Tests 2 passed (2)');
|
||||
|
||||
const lintE2eResults = runCLI(`lint ${appName}-e2e`);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
} from '../../utils/lint';
|
||||
import { NormalizedSchema, Schema } from './schema';
|
||||
import { createApplicationFiles } from './lib/create-application-files';
|
||||
import { updateJestConfig } from './lib/update-jest-config';
|
||||
import { updateSpecConfig } from './lib/update-jest-config';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
import { addProject } from './lib/add-project';
|
||||
import { addCypress } from './lib/add-cypress';
|
||||
@ -26,7 +26,7 @@ import reactInitGenerator from '../init/init';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
|
||||
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';
|
||||
import { viteConfigurationGenerator } from '@nrwl/vite';
|
||||
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
|
||||
|
||||
async function addLinting(host: Tree, options: NormalizedSchema) {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
@ -89,10 +89,20 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
uiFramework: 'react',
|
||||
project: options.projectName,
|
||||
newProject: true,
|
||||
includeVitest: true,
|
||||
});
|
||||
tasks.push(viteTask);
|
||||
}
|
||||
|
||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.projectName,
|
||||
inSourceTests: options.inSourceTests,
|
||||
});
|
||||
tasks.push(vitestTask);
|
||||
}
|
||||
|
||||
const lintTask = await addLinting(host, options);
|
||||
tasks.push(lintTask);
|
||||
|
||||
@ -100,7 +110,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
tasks.push(cypressTask);
|
||||
const jestTask = await addJest(host, options);
|
||||
tasks.push(jestTask);
|
||||
updateJestConfig(host, options);
|
||||
updateSpecConfig(host, options);
|
||||
const styledTask = addStyledModuleDependencies(host, options.styledModule);
|
||||
tasks.push(styledTask);
|
||||
const routingTask = addRouting(host, options);
|
||||
|
||||
@ -68,7 +68,10 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
||||
templateVariables
|
||||
);
|
||||
|
||||
if (options.unitTestRunner === 'none') {
|
||||
if (
|
||||
options.unitTestRunner === 'none' ||
|
||||
(options.unitTestRunner === 'vitest' && options.inSourceTests == true)
|
||||
) {
|
||||
host.delete(
|
||||
`${options.appProjectRoot}/src/app/${options.fileName}.spec.tsx`
|
||||
);
|
||||
@ -80,6 +83,18 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
|
||||
templateVariables
|
||||
);
|
||||
|
||||
if (options.unitTestRunner === 'vitest' && options.inSourceTests == true) {
|
||||
let originalAppContents = host
|
||||
.read(`${options.appProjectRoot}/src/app/${options.fileName}.tsx`)
|
||||
.toString();
|
||||
originalAppContents += `
|
||||
if (import.meta.vitest) {
|
||||
// add tests related to your file here
|
||||
// For more information please visit the Vitest docs site here: https://vitest.dev/guide/in-source.html
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
if (options.js) {
|
||||
toJS(host);
|
||||
}
|
||||
|
||||
@ -40,6 +40,10 @@ export function normalizeOptions(
|
||||
|
||||
assertValidStyle(options.style);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
options.unitTestRunner = 'vitest';
|
||||
}
|
||||
|
||||
options.routing = options.routing ?? false;
|
||||
options.strict = options.strict ?? true;
|
||||
options.classComponent = options.classComponent ?? false;
|
||||
|
||||
@ -2,8 +2,8 @@ import { updateJestConfigContent } from '../../../utils/jest-utils';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { offsetFromRoot, Tree, updateJson } from '@nrwl/devkit';
|
||||
|
||||
export function updateJestConfig(host: Tree, options: NormalizedSchema) {
|
||||
if (options.unitTestRunner !== 'jest') {
|
||||
export function updateSpecConfig(host: Tree, options: NormalizedSchema) {
|
||||
if (options.unitTestRunner === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -21,6 +21,10 @@ export function updateJestConfig(host: Tree, options: NormalizedSchema) {
|
||||
return json;
|
||||
});
|
||||
|
||||
if (options.unitTestRunner !== 'jest') {
|
||||
return;
|
||||
}
|
||||
|
||||
const configPath = `${options.appProjectRoot}/jest.config.${
|
||||
options.js ? 'js' : 'ts'
|
||||
}`;
|
||||
|
||||
@ -7,7 +7,8 @@ export interface Schema {
|
||||
skipFormat: boolean;
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
unitTestRunner: 'jest' | 'none';
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@ -103,10 +103,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files. Read more on the Vitest docs site: https://vitest.dev/guide/in-source.html"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["cypress", "none"],
|
||||
|
||||
@ -7,7 +7,7 @@ export interface Schema {
|
||||
skipFormat: boolean;
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
unitTestRunner: 'jest' | 'none';
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
e2eTestRunner: 'cypress' | 'none';
|
||||
linter: Linter;
|
||||
pascalCaseFiles?: boolean;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export interface InitSchema {
|
||||
unitTestRunner?: 'jest' | 'none';
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
|
||||
@ -47,6 +47,7 @@ import componentGenerator from '../component/component';
|
||||
import init from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
import { updateJestConfigContent } from '../../utils/jest-utils';
|
||||
import { vitestGenerator } from '@nrwl/vite';
|
||||
export interface NormalizedSchema extends Schema {
|
||||
name: string;
|
||||
fileName: string;
|
||||
@ -109,6 +110,13 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
);
|
||||
host.write(jestConfigPath, updatedContent);
|
||||
}
|
||||
} else if (options.unitTestRunner === 'vitest') {
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.name,
|
||||
inSourceTests: options.inSourceTests,
|
||||
});
|
||||
tasks.push(vitestTask);
|
||||
}
|
||||
|
||||
if (options.component) {
|
||||
@ -117,7 +125,9 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
project: options.name,
|
||||
flat: true,
|
||||
style: options.style,
|
||||
skipTests: options.unitTestRunner === 'none',
|
||||
skipTests:
|
||||
options.unitTestRunner === 'none' ||
|
||||
(options.unitTestRunner === 'vitest' && options.inSourceTests == true),
|
||||
export: true,
|
||||
routing: options.routing,
|
||||
js: options.js,
|
||||
|
||||
@ -11,7 +11,8 @@ export interface Schema {
|
||||
pascalCaseFiles?: boolean;
|
||||
routing?: boolean;
|
||||
appProject?: string;
|
||||
unitTestRunner: 'jest' | 'none';
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
linter: Linter;
|
||||
component?: boolean;
|
||||
publishable?: boolean;
|
||||
|
||||
@ -80,10 +80,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files."
|
||||
},
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Add tags to the library (used for linting).",
|
||||
|
||||
@ -8,7 +8,7 @@ export interface Schema {
|
||||
skipFormat: boolean;
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
unitTestRunner: 'jest' | 'none';
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
e2eTestRunner: 'cypress' | 'none';
|
||||
linter: Linter;
|
||||
pascalCaseFiles?: boolean;
|
||||
|
||||
@ -15,6 +15,11 @@
|
||||
"description": "Add Vite configuration to an application.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": false
|
||||
},
|
||||
"vitest": {
|
||||
"factory": "./src/generators/vitest/vitest-generator#vitestSchematic",
|
||||
"schema": "./src/generators/vitest/schema.json",
|
||||
"description": "Generate a vitest configuration"
|
||||
}
|
||||
},
|
||||
"generators": {
|
||||
@ -31,6 +36,11 @@
|
||||
"description": "Add Vite configuration to an application.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": false
|
||||
},
|
||||
"vitest": {
|
||||
"factory": "./src/generators/vitest/vitest-generator",
|
||||
"schema": "./src/generators/vitest/schema.json",
|
||||
"description": "Generate a vitest configuration"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './src/utils/versions';
|
||||
export { viteConfigurationGenerator } from './src/generators/configuration/configuration';
|
||||
export { vitestGenerator } from './src/generators/vitest/vitest-generator';
|
||||
|
||||
12
packages/vite/src/executors/test/schema.d.ts
vendored
12
packages/vite/src/executors/test/schema.d.ts
vendored
@ -1,9 +1,9 @@
|
||||
export interface VitestExecutorSchema {
|
||||
config: string;
|
||||
passWithNoTests: boolean;
|
||||
export interface VitestExecutorOptions {
|
||||
config?: string;
|
||||
passWithNoTests?: boolean;
|
||||
testNamePattern?: string;
|
||||
mode: 'test' | 'benchmark' | 'typecheck';
|
||||
mode?: 'test' | 'benchmark' | 'typecheck';
|
||||
reporters?: string[];
|
||||
watch: boolean;
|
||||
update: boolean;
|
||||
watch?: boolean;
|
||||
update?: boolean;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ExecutorContext } from '@nrwl/devkit';
|
||||
import { File, Reporter } from 'vitest';
|
||||
import { VitestExecutorSchema } from './schema';
|
||||
import { VitestExecutorOptions } from './schema';
|
||||
|
||||
class NxReporter implements Reporter {
|
||||
deferred: {
|
||||
@ -38,7 +38,7 @@ class NxReporter implements Reporter {
|
||||
}
|
||||
|
||||
export default async function* runExecutor(
|
||||
options: VitestExecutorSchema,
|
||||
options: VitestExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const { startVitest } = await (Function(
|
||||
|
||||
@ -91,4 +91,29 @@ describe('@nrwl/vite:configuration', () => {
|
||||
expect(tree.exists('apps/my-test-web-app/vite.config.ts')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vitest', () => {
|
||||
beforeAll(async () => {
|
||||
tree = createTreeWithEmptyV1Workspace();
|
||||
await mockReactAppGenerator(tree);
|
||||
const existing = 'existing';
|
||||
const existingVersion = '1.0.0';
|
||||
addDependenciesToPackageJson(
|
||||
tree,
|
||||
{ '@nrwl/vite': nxVersion, [existing]: existingVersion },
|
||||
{ [existing]: existingVersion }
|
||||
);
|
||||
await viteConfigurationGenerator(tree, {
|
||||
uiFramework: 'react',
|
||||
project: 'my-test-react-app',
|
||||
includeVitest: true,
|
||||
});
|
||||
});
|
||||
it('should create a vitest configuration if "includeVitest" is true', () => {
|
||||
const viteConfig = tree
|
||||
.read('apps/my-test-react-app/vite.config.ts')
|
||||
.toString();
|
||||
expect(viteConfig).toContain('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import {
|
||||
findServeAndBuildTargets,
|
||||
findExistingTargets,
|
||||
addOrChangeBuildTarget,
|
||||
addOrChangeServeTarget,
|
||||
editTsConfig,
|
||||
@ -16,6 +16,7 @@ import {
|
||||
} from '../../utils/generator-utils';
|
||||
|
||||
import initGenerator from '../init/init';
|
||||
import vitestGenerator from '../vitest/vitest-generator';
|
||||
import { Schema } from './schema';
|
||||
|
||||
export async function viteConfigurationGenerator(tree: Tree, schema: Schema) {
|
||||
@ -26,8 +27,8 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) {
|
||||
let serveTarget = 'serve';
|
||||
|
||||
if (!schema.newProject) {
|
||||
buildTarget = findServeAndBuildTargets(targets).buildTarget;
|
||||
serveTarget = findServeAndBuildTargets(targets).serveTarget;
|
||||
buildTarget = findExistingTargets(targets).buildTarget;
|
||||
serveTarget = findExistingTargets(targets).serveTarget;
|
||||
moveAndEditIndexHtml(tree, schema, buildTarget);
|
||||
editTsConfig(tree, schema);
|
||||
}
|
||||
@ -39,8 +40,19 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) {
|
||||
|
||||
addOrChangeBuildTarget(tree, schema, buildTarget);
|
||||
addOrChangeServeTarget(tree, schema, serveTarget);
|
||||
|
||||
writeViteConfig(tree, schema);
|
||||
|
||||
if (schema.includeVitest) {
|
||||
const vitestTask = await vitestGenerator(tree, {
|
||||
project: schema.project,
|
||||
uiFramework: schema.uiFramework,
|
||||
inSourceTests: schema.inSourceTests,
|
||||
skipViteConfig: true,
|
||||
});
|
||||
tasks.push(vitestTask);
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
|
||||
@ -2,4 +2,6 @@ export interface Schema {
|
||||
uiFramework: 'react' | 'none';
|
||||
project: string;
|
||||
newProject?: boolean;
|
||||
includeVitest?: boolean;
|
||||
inSourceTests?: boolean;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ Object {
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"@vitest/ui": "^0.9.3",
|
||||
"existing": "1.0.0",
|
||||
"jsdom": "~20.0.3",
|
||||
"vite": "^3.0.5",
|
||||
"vite-plugin-eslint": "^1.6.0",
|
||||
"vite-tsconfig-paths": "^3.5.2",
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
vitestUiVersion,
|
||||
vitestVersion,
|
||||
viteTsConfigPathsVersion,
|
||||
jsdomVersion,
|
||||
} from '../../utils/versions';
|
||||
import { Schema } from './schema';
|
||||
|
||||
@ -23,7 +24,7 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) {
|
||||
const devDependencies = {};
|
||||
const dependencies = {};
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencices = packageJson.devDependencices || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
|
||||
// base deps
|
||||
devDependencies['@nrwl/vite'] = nxVersion;
|
||||
@ -32,6 +33,7 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) {
|
||||
devDependencies['vite-tsconfig-paths'] = viteTsConfigPathsVersion;
|
||||
devDependencies['vitest'] = vitestVersion;
|
||||
devDependencies['@vitest/ui'] = vitestUiVersion;
|
||||
devDependencies['jsdom'] = jsdomVersion;
|
||||
|
||||
if (schema.uiFramework === 'react') {
|
||||
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
6
packages/vite/src/generators/vitest/schema.d.ts
vendored
Normal file
6
packages/vite/src/generators/vitest/schema.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export interface VitestGeneratorSchema {
|
||||
project: string;
|
||||
uiFramework: 'react' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
skipViteConfig?: boolean;
|
||||
}
|
||||
32
packages/vite/src/generators/vitest/schema.json
Normal file
32
packages/vite/src/generators/vitest/schema.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"cli": "nx",
|
||||
"$id": "Vitest",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"description": "Generate a vitest setup for a project.",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project to test.",
|
||||
"$default": { "$source": "projectName" }
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"enum": ["react", "none"],
|
||||
"default": "none",
|
||||
"description": "UI framework to use with vitest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not generate separate spec files and set up in-source testing"
|
||||
},
|
||||
"skipViteConfig": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Skip generating a vite config file"
|
||||
}
|
||||
},
|
||||
"required": ["project"]
|
||||
}
|
||||
109
packages/vite/src/generators/vitest/vitest-generator.ts
Normal file
109
packages/vite/src/generators/vitest/vitest-generator.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import {
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
offsetFromRoot,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nrwl/devkit';
|
||||
import {
|
||||
addOrChangeTestTarget,
|
||||
findExistingTargets,
|
||||
writeViteConfig,
|
||||
} from '../../utils/generator-utils';
|
||||
import { VitestGeneratorSchema } from './schema';
|
||||
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import initGenerator from '../init/init';
|
||||
|
||||
export async function vitestGenerator(
|
||||
tree: Tree,
|
||||
schema: VitestGeneratorSchema
|
||||
) {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
|
||||
const { targets, root } = readProjectConfiguration(tree, schema.project);
|
||||
let testTarget = findExistingTargets(targets).testTarget;
|
||||
|
||||
addOrChangeTestTarget(tree, schema, testTarget);
|
||||
|
||||
const initTask = await initGenerator(tree, {
|
||||
uiFramework: schema.uiFramework,
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
if (!schema.skipViteConfig) {
|
||||
writeViteConfig(tree, {
|
||||
...schema,
|
||||
includeVitest: true,
|
||||
});
|
||||
}
|
||||
|
||||
createFiles(tree, schema, root);
|
||||
updateTsConfig(tree, schema, root);
|
||||
|
||||
await formatFiles(tree);
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
function updateTsConfig(
|
||||
tree: Tree,
|
||||
options: VitestGeneratorSchema,
|
||||
projectRoot: string
|
||||
) {
|
||||
updateJson(tree, joinPathFragments(projectRoot, 'tsconfig.json'), (json) => {
|
||||
if (
|
||||
json.references &&
|
||||
!json.references.some((r) => r.path === './tsconfig.spec.json')
|
||||
) {
|
||||
json.references.push({
|
||||
path: './tsconfig.spec.json',
|
||||
});
|
||||
}
|
||||
return json;
|
||||
});
|
||||
|
||||
if (options.inSourceTests) {
|
||||
const tsconfigLibPath = joinPathFragments(projectRoot, 'tsconfig.lib.json');
|
||||
const tsconfigAppPath = joinPathFragments(projectRoot, 'tsconfig.app.json');
|
||||
if (tree.exists(tsconfigLibPath)) {
|
||||
updateJson(
|
||||
tree,
|
||||
joinPathFragments(projectRoot, 'tsconfig.lib.json'),
|
||||
(json) => {
|
||||
(json.compilerOptions.types ??= []).push('vitest/importMeta');
|
||||
return json;
|
||||
}
|
||||
);
|
||||
} else if (tree.exists(tsconfigAppPath)) {
|
||||
updateJson(
|
||||
tree,
|
||||
joinPathFragments(projectRoot, 'tsconfig.app.json'),
|
||||
(json) => {
|
||||
(json.compilerOptions.types ??= []).push('vitest/importMeta');
|
||||
return json;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFiles(
|
||||
tree: Tree,
|
||||
options: VitestGeneratorSchema,
|
||||
projectRoot: string
|
||||
) {
|
||||
generateFiles(tree, joinPathFragments(__dirname, 'files'), projectRoot, {
|
||||
tmpl: '',
|
||||
...options,
|
||||
projectRoot,
|
||||
offsetFromRoot: offsetFromRoot(projectRoot),
|
||||
});
|
||||
}
|
||||
|
||||
export default vitestGenerator;
|
||||
export const vitestSchematic = convertNxGenerator(vitestGenerator);
|
||||
162
packages/vite/src/generators/vitest/vitest.spec.ts
Normal file
162
packages/vite/src/generators/vitest/vitest.spec.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Tree, readProjectConfiguration } from '@nrwl/devkit';
|
||||
|
||||
import generator from './vitest-generator';
|
||||
import { VitestGeneratorSchema } from './schema';
|
||||
import { mockReactAppGenerator } from '../../utils/test-utils';
|
||||
|
||||
describe('vitest generator', () => {
|
||||
let appTree: Tree;
|
||||
const options: VitestGeneratorSchema = {
|
||||
project: 'my-test-react-app',
|
||||
uiFramework: 'react',
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
appTree = createTreeWithEmptyWorkspace();
|
||||
await mockReactAppGenerator(appTree);
|
||||
});
|
||||
|
||||
it('Should add the test target', async () => {
|
||||
await generator(appTree, options);
|
||||
const config = readProjectConfiguration(appTree, 'my-test-react-app');
|
||||
expect(config.targets['test']).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/vite:test",
|
||||
"options": Object {
|
||||
"passWithNoTests": true,
|
||||
},
|
||||
"outputs": Array [
|
||||
"{workspaceRoot}/coverage/{projectRoot}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('tsconfig', () => {
|
||||
it('should add a tsconfig.spec.json file', async () => {
|
||||
await generator(appTree, options);
|
||||
const tsconfig = JSON.parse(
|
||||
appTree.read('apps/my-test-react-app/tsconfig.json')?.toString() ?? '{}'
|
||||
);
|
||||
expect(tsconfig.references).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"path": "./tsconfig.app.json",
|
||||
},
|
||||
Object {
|
||||
"path": "./tsconfig.spec.json",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
const tsconfigSpec = JSON.parse(
|
||||
appTree.read('apps/my-test-react-app/tsconfig.spec.json')?.toString() ??
|
||||
'{}'
|
||||
);
|
||||
expect(tsconfigSpec).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"compilerOptions": Object {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": Array [
|
||||
"vitest/globals",
|
||||
"node",
|
||||
],
|
||||
},
|
||||
"extends": "./tsconfig.json",
|
||||
"include": Array [
|
||||
"vite.config.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add vitest/importMeta when inSourceTests is true', async () => {
|
||||
await generator(appTree, { ...options, inSourceTests: true });
|
||||
const tsconfig = JSON.parse(
|
||||
appTree.read('apps/my-test-react-app/tsconfig.app.json')?.toString() ??
|
||||
'{}'
|
||||
);
|
||||
expect(tsconfig.compilerOptions.types).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"vitest/importMeta",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vite.config', () => {
|
||||
it('should modify the vite.config.js file to include the test options', async () => {
|
||||
await generator(appTree, options);
|
||||
const viteConfig = appTree
|
||||
.read('apps/my-test-react-app/vite.config.ts')
|
||||
.toString();
|
||||
expect(viteConfig).toMatchInlineSnapshot(`
|
||||
"
|
||||
/// <reference types=\\"vitest\\" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '../../',
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insourceTests', () => {
|
||||
it('should add the insourceSource option in the vite config', async () => {
|
||||
await generator(appTree, { ...options, inSourceTests: true });
|
||||
const viteConfig = appTree
|
||||
.read('apps/my-test-react-app/vite.config.ts')
|
||||
.toString();
|
||||
expect(viteConfig).toMatchInlineSnapshot(`
|
||||
"
|
||||
/// <reference types=\\"vitest\\" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '../../',
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
'import.meta.vitest': undefined
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
includeSource: ['src/**/*.{js,ts,jsx,tsx}']
|
||||
},
|
||||
});"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { ViteBuildExecutorOptions } from '../executors/build/schema';
|
||||
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
|
||||
import { VitestExecutorOptions } from '../executors/test/schema';
|
||||
import { Schema } from '../generators/configuration/schema';
|
||||
|
||||
/**
|
||||
@ -27,18 +28,21 @@ import { Schema } from '../generators/configuration/schema';
|
||||
* they are using, and infer from the executor that the target
|
||||
* is a build target.
|
||||
*/
|
||||
export function findServeAndBuildTargets(targets: {
|
||||
export function findExistingTargets(targets: {
|
||||
[targetName: string]: TargetConfiguration;
|
||||
}): {
|
||||
buildTarget: string;
|
||||
serveTarget: string;
|
||||
testTarget: string;
|
||||
} {
|
||||
const returnObject: {
|
||||
buildTarget: string;
|
||||
serveTarget: string;
|
||||
testTarget: string;
|
||||
} = {
|
||||
buildTarget: 'build',
|
||||
serveTarget: 'serve',
|
||||
testTarget: 'test',
|
||||
};
|
||||
|
||||
Object.entries(targets).forEach(([target, targetConfig]) => {
|
||||
@ -68,9 +72,13 @@ export function findServeAndBuildTargets(targets: {
|
||||
case '@nxext/vite:build':
|
||||
returnObject.buildTarget = target;
|
||||
break;
|
||||
case '@nrwl/jest:jest':
|
||||
case 'nxext/vitest:vitest':
|
||||
returnObject.testTarget = target;
|
||||
default:
|
||||
returnObject.buildTarget = 'build';
|
||||
returnObject.serveTarget = 'serve';
|
||||
returnObject.testTarget = 'test';
|
||||
break;
|
||||
}
|
||||
});
|
||||
@ -78,6 +86,39 @@ export function findServeAndBuildTargets(targets: {
|
||||
return returnObject;
|
||||
}
|
||||
|
||||
export function addOrChangeTestTarget(
|
||||
tree: Tree,
|
||||
options: Schema,
|
||||
target: string
|
||||
) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
const targets = {
|
||||
...project.targets,
|
||||
};
|
||||
|
||||
const testOptions: VitestExecutorOptions = {
|
||||
passWithNoTests: true,
|
||||
};
|
||||
|
||||
if (targets[target]) {
|
||||
targets[target].executor = '@nrwl/vite:test';
|
||||
delete targets[target].options.jestConfig;
|
||||
} else {
|
||||
targets[target] = {
|
||||
executor: '@nrwl/vite:test',
|
||||
outputs: ['{projectRoot}/coverage'],
|
||||
options: testOptions,
|
||||
};
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, options.project, {
|
||||
...project,
|
||||
targets: {
|
||||
...targets,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function addOrChangeBuildTarget(
|
||||
tree: Tree,
|
||||
options: Schema,
|
||||
@ -315,9 +356,22 @@ export function writeViteConfig(tree: Tree, options: Schema) {
|
||||
|
||||
let viteConfigContent = '';
|
||||
|
||||
const testOption = `test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
${
|
||||
options.inSourceTests ? `includeSource: ['src/**/*.{js,ts,jsx,tsx}']` : ''
|
||||
}
|
||||
},`;
|
||||
|
||||
const defineOption = `define: {
|
||||
'import.meta.vitest': undefined
|
||||
},`;
|
||||
|
||||
switch (options.uiFramework) {
|
||||
case 'react':
|
||||
viteConfigContent = `
|
||||
${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
@ -330,10 +384,13 @@ export function writeViteConfig(tree: Tree, options: Schema) {
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
${options.inSourceTests ? defineOption : ''}
|
||||
${options.includeVitest ? testOption : ''}
|
||||
});`;
|
||||
break;
|
||||
case 'none':
|
||||
viteConfigContent = `
|
||||
${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
import { defineConfig } from 'vite';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
|
||||
@ -344,6 +401,8 @@ export function writeViteConfig(tree: Tree, options: Schema) {
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
${options.inSourceTests ? defineOption : ''}
|
||||
${options.includeVitest ? testOption : ''}
|
||||
});`;
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -39,6 +39,32 @@ export function mockReactAppGenerator(tree: Tree): Tree {
|
||||
}
|
||||
`
|
||||
);
|
||||
tree.write(
|
||||
`apps/${appName}/tsconfig.app.json`,
|
||||
`{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc"
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
tree.write(
|
||||
`apps/${appName}/src/index.html`,
|
||||
|
||||
@ -7,3 +7,4 @@ export const vitePluginReactVersion = '^2.2.0';
|
||||
export const vitePluginVueVersion = '^3.2.0';
|
||||
export const vitePluginVueJsxVersion = '^2.1.1';
|
||||
export const viteTsConfigPathsVersion = '^3.5.2';
|
||||
export const jsdomVersion = '~20.0.3';
|
||||
|
||||
@ -24,7 +24,7 @@ import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
||||
import { viteConfigurationGenerator } from '@nrwl/vite';
|
||||
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
|
||||
|
||||
import { swcLoaderVersion } from '../../utils/versions';
|
||||
import { webInitGenerator } from '../init/init';
|
||||
@ -203,10 +203,20 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
uiFramework: 'react',
|
||||
project: options.projectName,
|
||||
newProject: true,
|
||||
includeVitest: true,
|
||||
});
|
||||
tasks.push(viteTask);
|
||||
}
|
||||
|
||||
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'none',
|
||||
project: options.projectName,
|
||||
inSourceTests: options.inSourceTests,
|
||||
});
|
||||
tasks.push(vitestTask);
|
||||
}
|
||||
|
||||
const lintTask = await lintProjectGenerator(host, {
|
||||
linter: options.linter,
|
||||
project: options.projectName,
|
||||
@ -273,6 +283,10 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
|
||||
? options.tags.split(',').map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
options.unitTestRunner = 'vitest';
|
||||
}
|
||||
|
||||
options.style = options.style || 'css';
|
||||
options.linter = options.linter || Linter.EsLint;
|
||||
options.unitTestRunner = options.unitTestRunner || 'jest';
|
||||
|
||||
@ -9,7 +9,8 @@ export interface Schema {
|
||||
skipFormat?: boolean;
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
unitTestRunner?: 'jest' | 'none';
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
linter?: Linter;
|
||||
standaloneConfig?: boolean;
|
||||
|
||||
@ -73,10 +73,15 @@
|
||||
},
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "none"],
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests",
|
||||
"default": "jest"
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When using Vitest, separate spec files will not be generated and instead will be included within the source files."
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["cypress", "none"],
|
||||
|
||||
2
packages/web/src/generators/init/schema.d.ts
vendored
2
packages/web/src/generators/init/schema.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
export interface Schema {
|
||||
bundler?: 'webpack' | 'none' | 'vite';
|
||||
unitTestRunner?: 'jest' | 'none';
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user