diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts index 3e341359e0683..bd1359387b2e5 100644 --- a/e2e/vite/src/vite.test.ts +++ b/e2e/vite/src/vite.test.ts @@ -1,6 +1,7 @@ import { cleanupProject, createFile, + directoryExists, exists, fileExists, killPorts, @@ -192,7 +193,7 @@ describe('Vite Plugin', () => { describe('should be able to create libs that use vitest', () => { const lib = uniq('my-lib'); beforeEach(() => { - proj = newProject(); + proj = newProject({ name: uniq('vite-proj') }); }), 100_000; @@ -206,6 +207,55 @@ describe('Vite Plugin', () => { ); }, 100_000); + it('should collect coverage', () => { + runCLI(`generate @nrwl/react:lib ${lib} --unitTestRunner=vitest`); + updateFile(`libs/${lib}/vite.config.ts`, () => { + return `import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import viteTsConfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + server: { + port: 4200, + host: 'localhost', + }, + plugins: [ + react(), + viteTsConfigPaths({ + root: './', + }), + ], + test: { + globals: true, + cache: { + dir: './node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + coverage: { + provider: "c8", + enabled: true, + lines: 100, + statements: 100, + functions: 100, + branches: 1000, + } + }, +}); +`; + }); + + const coverageDir = `${tmpProjPath()}/coverage/libs/${lib}`; + + const results = runCLI(`test ${lib} --coverage`, { silenceError: true }); + + expect(results).toContain( + `Running target test for project ${lib} failed` + ); + expect(results).toContain(`ERROR: Coverage`); + expect(directoryExists(coverageDir)).toBeTruthy(); + }, 100_000); + it('should be able to run tests with inSourceTests set to true', async () => { runCLI( `generate @nrwl/react:lib ${lib} --unitTestRunner=vitest --inSourceTests` diff --git a/packages/vite/migrations.json b/packages/vite/migrations.json index 263ae3c8fb330..332a53a57c42f 100644 --- a/packages/vite/migrations.json +++ b/packages/vite/migrations.json @@ -11,6 +11,12 @@ "version": "15.3.4-beta.0", "description": "Set the mode in configurations to match the configurationName.", "factory": "./src/migrations/update-15-3-4/set-mode-in-configuration" + }, + "update-test-path-placeholder-vars": { + "cli": "nx", + "version": "15.4.3-beta.0", + "description": "Update @nrwl/vite:test reportsDirectory and outputs properties to point to correct paths.", + "factory": "./src/migrations/update-15-4-3/update-report-directory" } }, "packageJsonUpdates": { diff --git a/packages/vite/src/executors/test/vitest.impl.ts b/packages/vite/src/executors/test/vitest.impl.ts index f7f063363f224..a038cb15ce927 100644 --- a/packages/vite/src/executors/test/vitest.impl.ts +++ b/packages/vite/src/executors/test/vitest.impl.ts @@ -75,7 +75,9 @@ export default async function* runExecutor( } for await (const report of nxReporter) { - hasErrors = report.hasErrors; + // vitest sets the exitCode = 1 when code coverage isn't met + hasErrors = + report.hasErrors || (process.exitCode && process.exitCode !== 0); } return { diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts index 1900936bee6df..25e29f5fc3877 100644 --- a/packages/vite/src/generators/vitest/vitest-generator.ts +++ b/packages/vite/src/generators/vitest/vitest-generator.ts @@ -16,7 +16,6 @@ import { createOrEditViteConfig, } from '../../utils/generator-utils'; import { VitestGeneratorSchema } from './schema'; - import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import initGenerator from '../init/init'; import { @@ -41,7 +40,7 @@ export async function vitestGenerator( addOrChangeTestTarget(tree, schema, testTarget); - const initTask = await initGenerator(tree, { + const initTask = initGenerator(tree, { uiFramework: schema.uiFramework, }); tasks.push(initTask); diff --git a/packages/vite/src/generators/vitest/vitest.spec.ts b/packages/vite/src/generators/vitest/vitest.spec.ts index 40a85139621b5..5a19ab0dd522c 100644 --- a/packages/vite/src/generators/vitest/vitest.spec.ts +++ b/packages/vite/src/generators/vitest/vitest.spec.ts @@ -1,5 +1,9 @@ import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; -import { Tree, readProjectConfiguration } from '@nrwl/devkit'; +import { + Tree, + readProjectConfiguration, + updateProjectConfiguration, +} from '@nrwl/devkit'; import generator from './vitest-generator'; import { VitestGeneratorSchema } from './schema'; @@ -20,7 +24,7 @@ describe('vitest generator', () => { appTree = createTreeWithEmptyWorkspace(); }); - it('Should add the test target', async () => { + it('Should add the test target to existing test target', async () => { mockReactAppGenerator(appTree); await generator(appTree, options); const config = readProjectConfiguration(appTree, 'my-test-react-app'); @@ -37,6 +41,30 @@ describe('vitest generator', () => { `); }); + it('should add the test target if its missing', async () => { + mockReactAppGenerator(appTree); + const projectConfig = readProjectConfiguration( + appTree, + 'my-test-react-app' + ); + delete projectConfig.targets.test; + updateProjectConfiguration(appTree, 'my-test-react-app', projectConfig); + 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, + "reportsDirectory": "../../coverage/apps/my-test-react-app", + }, + "outputs": Array [ + "coverage/apps/my-test-react-app", + ], + } + `); + }); + describe('tsconfig', () => { it('should add a tsconfig.spec.json file', async () => { mockReactAppGenerator(appTree); diff --git a/packages/vite/src/migrations/update-15-4-3/update-report-directory.spec.ts b/packages/vite/src/migrations/update-15-4-3/update-report-directory.spec.ts new file mode 100644 index 0000000000000..f64e676649ba9 --- /dev/null +++ b/packages/vite/src/migrations/update-15-4-3/update-report-directory.spec.ts @@ -0,0 +1,148 @@ +import { + addProjectConfiguration, + readProjectConfiguration, + Tree, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { updateReportDirectoryPlaceholders } from './update-report-directory'; + +describe('Update Report Directory Vitest Migration', () => { + let tree: Tree; + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should migrate', () => { + addVitestProjects(tree, { name: 'project' }); + + updateReportDirectoryPlaceholders(tree); + expect(readProjectConfiguration(tree, 'project-one').targets) + .toMatchInlineSnapshot(` + Object { + "test": Object { + "executor": "@nrwl/vite:test", + "options": Object { + "passWithNoTests": true, + "reportsDirectory": "../../coverge/packages/project-one", + }, + "outputs": Array [ + "coverage/packages/project-one", + ], + }, + } + `); + expect(readProjectConfiguration(tree, 'project-two').targets) + .toMatchInlineSnapshot(` + Object { + "custom-test": Object { + "configurations": Object { + "ci": Object { + "reportsDirectory": "coverge/project-two", + }, + }, + "executor": "@nrwl/vite:test", + "options": Object { + "passWithNoTests": true, + }, + "outputs": Array [ + "coverage/project-two", + "dist/coverage/else.txt", + ], + }, + "test": Object { + "executor": "@nrwl/vite:test", + "options": Object { + "passWithNoTests": true, + "reportsDirectory": "coverge/project-two", + }, + }, + } + `); + }); + it('should be idempotent', () => { + addVitestProjects(tree, { name: 'project' }); + + const expectedProjectOneConfig = { + test: { + outputs: ['coverage/packages/project-one'], + executor: '@nrwl/vite:test', + options: { + reportsDirectory: '../../coverge/packages/project-one', + passWithNoTests: true, + }, + }, + }; + + const expectedProjectTwoConfig = { + 'custom-test': { + executor: '@nrwl/vite:test', + outputs: ['coverage/project-two', 'dist/coverage/else.txt'], + options: { + passWithNoTests: true, + }, + configurations: { + ci: { + reportsDirectory: 'coverge/project-two', + }, + }, + }, + test: { + executor: '@nrwl/vite:test', + options: { + reportsDirectory: 'coverge/project-two', + passWithNoTests: true, + }, + }, + }; + + updateReportDirectoryPlaceholders(tree); + const projOneConfig = readProjectConfiguration(tree, 'project-one'); + expect(projOneConfig.targets).toEqual(expectedProjectOneConfig); + const projTwoConfig = readProjectConfiguration(tree, 'project-two'); + expect(projTwoConfig.targets).toEqual(expectedProjectTwoConfig); + }); +}); + +function addVitestProjects(tree: Tree, options: { name: string }) { + addProjectConfiguration(tree, options.name + '-one', { + name: options.name + '-one', + sourceRoot: `packages/${options.name}-one/src`, + root: `packages/${options.name}-one`, + targets: { + test: { + outputs: ['{projectRoot}/coverage'], + executor: '@nrwl/vite:test', + options: { + reportsDirectory: '{workspaceRoot}/coverge/{projectRoot}', + passWithNoTests: true, + }, + }, + }, + }); + addProjectConfiguration(tree, options.name + '-two', { + name: options.name + '-two', + sourceRoot: 'src', + root: '.', + targets: { + 'custom-test': { + executor: '@nrwl/vite:test', + outputs: ['{projectRoot}/coverage', 'dist/coverage/else.txt'], + options: { + passWithNoTests: true, + }, + configurations: { + ci: { + reportsDirectory: '{workspaceRoot}/coverge/{projectRoot}', + }, + }, + }, + test: { + executor: '@nrwl/vite:test', + options: { + reportsDirectory: '{workspaceRoot}/coverge/{projectRoot}', + passWithNoTests: true, + }, + }, + }, + }); +} diff --git a/packages/vite/src/migrations/update-15-4-3/update-report-directory.ts b/packages/vite/src/migrations/update-15-4-3/update-report-directory.ts new file mode 100644 index 0000000000000..f6d62353b16f1 --- /dev/null +++ b/packages/vite/src/migrations/update-15-4-3/update-report-directory.ts @@ -0,0 +1,49 @@ +import { + getProjects, + offsetFromRoot, + Tree, + updateProjectConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; +import { VitestExecutorOptions } from '../../executors/test/schema'; + +export function updateReportDirectoryPlaceholders(tree: Tree) { + const projects = getProjects(tree); + forEachExecutorOptions( + tree, + '@nrwl/vite:test', + (options, projectName, targetName, configName) => { + const projectConfig = projects.get(projectName); + const coverageOutput = + projectConfig.root === '.' ? projectName : projectConfig.root; + + if (options.reportsDirectory) { + options.reportsDirectory = options.reportsDirectory + .replace( + '{workspaceRoot}/', + projectConfig.root === '.' ? '' : offsetFromRoot(projectConfig.root) + ) + .replace('{projectRoot}', coverageOutput); + if (configName) { + projectConfig.targets[targetName].configurations[configName] = + options; + } else { + projectConfig.targets[targetName].options = options; + } + if (projectConfig.targets[targetName].outputs) { + projectConfig.targets[targetName].outputs = projectConfig.targets[ + targetName + ].outputs.map((output) => + output.replace( + '{projectRoot}/coverage', + `coverage/${coverageOutput}` + ) + ); + } + updateProjectConfiguration(tree, projectName, projectConfig); + } + } + ); +} + +export default updateReportDirectoryPlaceholders; diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index ebdffbd3be1f4..db763c2ad76f6 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -208,12 +208,16 @@ export function addOrChangeTestTarget( ) { const project = readProjectConfiguration(tree, options.project); + const coveragePath = joinPathFragments( + 'coverage', + project.root === '.' ? options.project : project.root + ); const testOptions: VitestExecutorOptions = { passWithNoTests: true, + // vitest runs in the project root so we have to offset to the workspaceRoot reportsDirectory: joinPathFragments( - '{workspaceRoot}', - 'coverage', - '{projectRoot}' + offsetFromRoot(project.root), + coveragePath ), }; @@ -226,7 +230,7 @@ export function addOrChangeTestTarget( } project.targets[target] = { executor: '@nrwl/vite:test', - outputs: ['{projectRoot}/coverage'], + outputs: [coveragePath], options: testOptions, }; }