Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export class VitestExecutor implements TestExecutor {
projectName,
buildResultFiles: this.buildResultFiles,
testFileToEntryPoint: this.testFileToEntryPoint,
setupFiles: testSetupFiles,
});

const debugOptions = debug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface PluginOptions {
projectName: string;
buildResultFiles: ReadonlyMap<string, ResultFile>;
testFileToEntryPoint: ReadonlyMap<string, string>;
setupFiles: readonly string[];
}

type VitestCoverageOption = Exclude<InlineConfig['coverage'], undefined>;
Expand Down Expand Up @@ -313,8 +314,13 @@ async function loadResultFile(file: ResultFile): Promise<string> {
}

export function createVitestPlugins(pluginOptions: PluginOptions): Vite.Plugin[] {
const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions;
const { workspaceRoot, buildResultFiles, testFileToEntryPoint, setupFiles } = pluginOptions;
const isWindows = platform() === 'win32';
const setupFileSet = new Set(
setupFiles.map((file) =>
toPosixPath(path.isAbsolute(file) ? file : path.join(workspaceRoot, file)),
),
);
let vitestConfig: ResolvedConfig;

return [
Expand Down Expand Up @@ -387,7 +393,11 @@ export function createVitestPlugins(pluginOptions: PluginOptions): Vite.Plugin[]
if (entryPoint) {
outputPath = entryPoint + '.js';

if (vitestConfig?.coverage?.enabled) {
// Setup files must not be wrapped in a virtual import stub because Vitest only invalidates
// the setup file itself between test files; wrapping it would cause the underlying bundle
// to be cached, preventing per-test hooks from running on subsequent test files.
const isSetupFile = setupFileSet.has(id);
if (vitestConfig?.coverage?.enabled && !isSetupFile) {
// To support coverage exclusion of the actual test file, the virtual
// test entry point only references the built and bundled intermediate file.
// If vitest supported an "excludeOnlyAfterRemap" option, this could be removed completely.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,68 @@ describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});

it('should run setup file hooks for each spec file when coverage is enabled', async () => {
await harness.writeFiles({
'custom-vitest.config.mts': `
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
fileParallelism: false,
},
});
`,
'src/setup.ts': `
import { afterEach, beforeEach, expect } from 'vitest';
const global = globalThis as typeof globalThis & {
setupHookCalls?: string[];
};
const setupHookCalls = (global.setupHookCalls ??= []);
beforeEach(() => {
const testName = expect.getState().currentTestName ?? '';
setupHookCalls.push('beforeEach:' + testName);
});
afterEach(() => {
const testName = expect.getState().currentTestName ?? '';
setupHookCalls.push('afterEach:' + testName);
});
`,
'src/app/app.component.spec.ts': `
import { expect, it } from 'vitest';
it('runs setup hooks for first test in app.component.spec', () => {
const global = globalThis as typeof globalThis & { setupHookCalls?: string[] };
expect(global.setupHookCalls).toContain('beforeEach:runs setup hooks for first test in app.component.spec');
});
it('runs setup hooks for second test in app.component.spec', () => {
const global = globalThis as typeof globalThis & { setupHookCalls?: string[] };
expect(global.setupHookCalls).toContain('beforeEach:runs setup hooks for second test in app.component.spec');
expect(global.setupHookCalls).toContain('afterEach:runs setup hooks for first test in app.component.spec');
});
`,
'src/app/second.spec.ts': `
import { expect, it } from 'vitest';
it('runs setup hooks for first test in second.spec', () => {
const global = globalThis as typeof globalThis & { setupHookCalls?: string[] };
expect(global.setupHookCalls).toContain('beforeEach:runs setup hooks for first test in second.spec');
});
it('runs setup hooks for second test in second.spec', () => {
const global = globalThis as typeof globalThis & { setupHookCalls?: string[] };
expect(global.setupHookCalls).toContain('beforeEach:runs setup hooks for second test in second.spec');
expect(global.setupHookCalls).toContain('afterEach:runs setup hooks for first test in second.spec');
});
`,
});

harness.useTarget('test', {
...BASE_OPTIONS,
coverage: true,
runnerConfig: 'custom-vitest.config.mts',
setupFiles: ['src/setup.ts'],
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});
});
});
Loading