Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(testing): correctly set task status for failed coverage reports #14008

Merged
merged 1 commit into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion e2e/vite/src/vite.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
cleanupProject,
createFile,
directoryExists,
exists,
fileExists,
killPorts,
Expand Down Expand Up @@ -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;

Expand All @@ -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`
Expand Down
6 changes: 6 additions & 0 deletions packages/vite/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 3 additions & 1 deletion packages/vite/src/executors/test/vitest.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 1 addition & 2 deletions packages/vite/src/generators/vitest/vitest-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down
32 changes: 30 additions & 2 deletions packages/vite/src/generators/vitest/vitest.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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');
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
},
},
},
});
}
Original file line number Diff line number Diff line change
@@ -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<VitestExecutorOptions>(
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;
12 changes: 8 additions & 4 deletions packages/vite/src/utils/generator-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
};

Expand All @@ -226,7 +230,7 @@ export function addOrChangeTestTarget(
}
project.targets[target] = {
executor: '@nrwl/vite:test',
outputs: ['{projectRoot}/coverage'],
outputs: [coveragePath],
options: testOptions,
};
}
Expand Down