Skip to content

Commit

Permalink
Merge branch 'next' into revert-29820-revert-29807-nx-20
Browse files Browse the repository at this point in the history
  • Loading branch information
ndelangen authored Dec 6, 2024
2 parents 7331c98 + 4d550be commit b04c5a1
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 104 deletions.
1 change: 1 addition & 0 deletions code/addons/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "^7.6.3",
"sirv": "^2.0.4",
"slash": "^5.0.0",
"strip-ansi": "^7.1.0",
"tinyglobby": "^0.2.10",
Expand Down
21 changes: 16 additions & 5 deletions code/addons/test/src/node/boot-test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ const MAX_START_TIME = 30000;
// which is at the root. Then, from the root, we want to load `node/vitest.mjs`
const vitestModulePath = join(__dirname, 'node', 'vitest.mjs');

// Events that were triggered before Vitest was ready are queued up and resent once it's ready
const eventQueue: { type: string; args: any[] }[] = [];

let child: null | ChildProcess;
let ready = false;

const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: any[]) => {
const bootTestRunner = async (channel: Channel) => {
let stderr: string[] = [];

function reportFatalError(e: any) {
Expand Down Expand Up @@ -58,6 +61,7 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a

const exit = (code = 0) => {
killChild();
eventQueue.length = 0;
process.exit(code);
};

Expand All @@ -81,9 +85,10 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a

child.on('message', (result: any) => {
if (result.type === 'ready') {
// Resend the event that triggered the boot sequence, now that the child is ready to handle it
if (initEvent && initArgs) {
child?.send({ type: initEvent, args: initArgs, from: 'server' });
// Resend events that triggered (during) the boot sequence, now that Vitest is ready
while (eventQueue.length) {
const { type, args } = eventQueue.shift();
child?.send({ type, args, from: 'server' });
}

// Forward all events from the channel to the child process
Expand Down Expand Up @@ -124,14 +129,18 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a

await Promise.race([startChildProcess(), timeout]).catch((e) => {
reportFatalError(e);
eventQueue.length = 0;
throw e;
});
};

export const runTestRunner = async (channel: Channel, initEvent?: string, initArgs?: any[]) => {
if (!ready && initEvent) {
eventQueue.push({ type: initEvent, args: initArgs });
}
if (!child) {
ready = false;
await bootTestRunner(channel, initEvent, initArgs);
await bootTestRunner(channel);
ready = true;
}
};
Expand All @@ -141,4 +150,6 @@ export const killTestRunner = () => {
child.kill();
child = null;
}
ready = false;
eventQueue.length = 0;
};
28 changes: 16 additions & 12 deletions code/addons/test/src/node/test-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ export class TestManager {
this.vitestManager.startVitest().then(() => options.onReady?.());
}

async restartVitest({ watchMode, coverage }: { watchMode: boolean; coverage: boolean }) {
await this.vitestManager.vitest?.runningPromise;
await this.vitestManager.closeVitest();
await this.vitestManager.startVitest({ watchMode, coverage });
}

async handleConfigChange(
payload: TestingModuleConfigChangePayload<{ coverage: boolean; a11y: boolean }>
) {
Expand All @@ -54,7 +48,10 @@ export class TestManager {
if (this.coverage !== payload.config.coverage) {
try {
this.coverage = payload.config.coverage;
await this.restartVitest({ watchMode: this.watchMode, coverage: this.coverage });
await this.vitestManager.restartVitest({
watchMode: this.watchMode,
coverage: this.coverage,
});
} catch (e) {
const isV8 = e.message?.includes('@vitest/coverage-v8');
const isIstanbul = e.message?.includes('@vitest/coverage-istanbul');
Expand All @@ -76,7 +73,7 @@ export class TestManager {

if (this.watchMode !== payload.watchMode) {
this.watchMode = payload.watchMode;
await this.restartVitest({ watchMode: this.watchMode, coverage: false });
await this.vitestManager.restartVitest({ watchMode: this.watchMode, coverage: false });
}
} catch (e) {
this.reportFatalError('Failed to change watch mode', e);
Expand All @@ -88,32 +85,39 @@ export class TestManager {
if (payload.providerId !== TEST_PROVIDER_ID) {
return;
}

const allTestsRun = (payload.storyIds ?? []).length === 0;

if (payload.config && this.coverage !== payload.config.coverage) {
this.coverage = payload.config.coverage;
}

const allTestsRun = (payload.storyIds ?? []).length === 0;
if (this.coverage) {
/*
If we have coverage enabled and we're running all stories,
we have to restart Vitest AND disable watch mode otherwise the coverage report will be incorrect,
Vitest behaves wonky when re-using the same Vitest instance but with watch mode disabled,
among other things it causes the coverage report to be incorrect and stale.
If we're only running a subset of stories, we have to temporarily disable coverage,
as a coverage report for a subset of stories is not useful.
*/
await this.restartVitest({
await this.vitestManager.restartVitest({
watchMode: allTestsRun ? false : this.watchMode,
coverage: allTestsRun,
});
} else {
await this.vitestManager.vitestRestartPromise;
}

await this.vitestManager.runTests(payload);

if (this.coverage && !allTestsRun) {
// Re-enable coverage if it was temporarily disabled because of a subset of stories was run
await this.restartVitest({ watchMode: this.watchMode, coverage: this.coverage });
await this.vitestManager.restartVitest({
watchMode: this.watchMode,
coverage: this.coverage,
});
}
} catch (e) {
this.reportFatalError('Failed to run tests', e);
Expand Down
28 changes: 25 additions & 3 deletions code/addons/test/src/node/vitest-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export class VitestManager {

vitestStartupCounter = 0;

vitestRestartPromise: Promise<void> | null = null;

storyCountForCurrentRun: number = 0;

constructor(private testManager: TestManager) {}
Expand Down Expand Up @@ -99,12 +101,30 @@ export class VitestManager {
}
}

async restartVitest({ watchMode, coverage }: { watchMode: boolean; coverage: boolean }) {
await this.vitestRestartPromise;
this.vitestRestartPromise = new Promise(async (resolve, reject) => {
try {
await this.vitest?.runningPromise;
await this.closeVitest();
await this.startVitest({ watchMode, coverage });
resolve();
} catch (e) {
reject(e);
} finally {
this.vitestRestartPromise = null;
}
});
return this.vitestRestartPromise;
}

private updateLastChanged(filepath: string) {
const projects = this.vitest!.getModuleProjects(filepath);
projects.forEach(({ server, browser }) => {
const serverMods = server.moduleGraph.getModulesByFile(filepath);
serverMods?.forEach((mod) => server.moduleGraph.invalidateModule(mod));

if (server) {
const serverMods = server.moduleGraph.getModulesByFile(filepath);
serverMods?.forEach((mod) => server.moduleGraph.invalidateModule(mod));
}
if (browser) {
const browserMods = browser.vite.moduleGraph.getModulesByFile(filepath);
browserMods?.forEach((mod) => browser.vite.moduleGraph.invalidateModule(mod));
Expand Down Expand Up @@ -148,6 +168,8 @@ export class VitestManager {
async runTests(requestPayload: TestingModuleRunRequestPayload<Config>) {
if (!this.vitest) {
await this.startVitest();
} else {
await this.vitestRestartPromise;
}

this.resetTestNamePattern();
Expand Down
25 changes: 24 additions & 1 deletion code/addons/test/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import {
normalizeStories,
validateConfigurationFiles,
} from 'storybook/internal/common';
import { StoryIndexGenerator } from 'storybook/internal/core-server';
import { StoryIndexGenerator, mapStaticDir } from 'storybook/internal/core-server';
import { readConfig, vitestTransform } from 'storybook/internal/csf-tools';
import { MainFileMissingError } from 'storybook/internal/server-errors';
import type { DocsOptions, StoriesEntry } from 'storybook/internal/types';

import { join, resolve } from 'pathe';
import sirv from 'sirv';
import { convertPathToPattern } from 'tinyglobby';

import type { InternalOptions, UserOptions } from './types';
Expand Down Expand Up @@ -63,6 +64,7 @@ export const storybookTest = (options?: UserOptions): Plugin => {
let previewLevelTags: string[];
let storiesGlobs: StoriesEntry[];
let storiesFiles: string[];
const statics: ReturnType<typeof mapStaticDir>[] = [];

return {
name: 'vite-plugin-storybook-test',
Expand Down Expand Up @@ -111,6 +113,15 @@ export const storybookTest = (options?: UserOptions): Plugin => {
const framework = await presets.apply('framework', undefined);
const frameworkName = typeof framework === 'string' ? framework : framework.name;
const storybookEnv = await presets.apply('env', {});
const staticDirs = await presets.apply('staticDirs', []);

for (const staticDir of staticDirs) {
try {
statics.push(mapStaticDir(staticDir, configDir));
} catch (e) {
console.warn(e);
}
}

// If we end up needing to know if we are running in browser mode later
// const isRunningInBrowserMode = config.plugins.find((plugin: Plugin) =>
Expand Down Expand Up @@ -192,6 +203,18 @@ export const storybookTest = (options?: UserOptions): Plugin => {
config.define.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = 'false';
}
},
configureServer(server) {
statics.map(({ staticPath, targetEndpoint }) => {
server.middlewares.use(
targetEndpoint,
sirv(staticPath, {
dev: true,
etag: true,
extensions: [],
})
);
});
},
async transform(code, id) {
if (process.env.VITEST !== 'true') {
return code;
Expand Down
1 change: 1 addition & 0 deletions code/core/src/core-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from './build-static';
export * from './build-dev';
export * from './withTelemetry';
export { default as build } from './standalone';
export { mapStaticDir } from './utils/server-statics';
export { StoryIndexGenerator } from './utils/StoryIndexGenerator';
58 changes: 28 additions & 30 deletions code/core/src/core-server/presets/common-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,39 +67,37 @@ export const favicon = async (
? staticDirsValue.map((dir) => (typeof dir === 'string' ? dir : `${dir.from}:${dir.to}`))
: [];

if (statics && statics.length > 0) {
const lists = await Promise.all(
statics.map(async (dir) => {
const results = [];
const normalizedDir =
staticDirsValue && !isAbsolute(dir)
? getDirectoryFromWorkingDir({
configDir: options.configDir,
workingDir: process.cwd(),
directory: dir,
})
: dir;

const { staticPath, targetEndpoint } = await parseStaticDir(normalizedDir);

if (targetEndpoint === '/') {
const url = 'favicon.svg';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
if (statics.length > 0) {
const lists = statics.map((dir) => {
const results = [];
const normalizedDir =
staticDirsValue && !isAbsolute(dir)
? getDirectoryFromWorkingDir({
configDir: options.configDir,
workingDir: process.cwd(),
directory: dir,
})
: dir;

const { staticPath, targetEndpoint } = parseStaticDir(normalizedDir);

if (targetEndpoint === '/') {
const url = 'favicon.svg';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
if (targetEndpoint === '/') {
const url = 'favicon.ico';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
}
if (targetEndpoint === '/') {
const url = 'favicon.ico';
const path = join(staticPath, url);
if (existsSync(path)) {
results.push(path);
}
}

return results;
})
);
return results;
});
const flatlist = lists.reduce((l1, l2) => l1.concat(l2), []);

if (flatlist.length > 1) {
Expand Down
Loading

0 comments on commit b04c5a1

Please sign in to comment.