diff --git a/AGENTS.md b/AGENTS.md
index 6b556d268f68..e94316c2e490 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,7 +10,8 @@ Storybook is a large TypeScript monorepo. The git root is the repo root, the mai
- **Base branch**: `next` (all PRs should target `next`, not `main`)
- **Node.js**: `22.22.3` (see `.nvmrc`) — supports `.ts` natively via type stripping (no loader needed)
-- **Package Manager**: Yarn Berry
+- **Package Manager**: Yarn Berry (use `node .yarn/releases/yarn-4.18.0.cjs` if no global Yarn is configured)
+- **Vitest**: 5.x for local development; addon compatibility tests also cover the Vitest 4 test-name separator.
- **Task orchestration**: NX plus the custom `yarn task` runner
- **Linting**: oxlint (root `.oxlintrc.json`, extended by `code/.oxlintrc.json` and `scripts/.oxlintrc.json`; custom rules load via `jsPlugins`). ESLint is no longer used for repo linting — `code/lib/eslint-plugin` remains as the published `eslint-plugin-storybook` package.
- **Formatting**: oxfmt (root `.oxfmtrc.json`)
diff --git a/code/addons/a11y/src/components/A11YPanel.test.tsx b/code/addons/a11y/src/components/A11YPanel.test.tsx
index 12040e9eb902..623c2d17e2b7 100644
--- a/code/addons/a11y/src/components/A11YPanel.test.tsx
+++ b/code/addons/a11y/src/components/A11YPanel.test.tsx
@@ -1,5 +1,5 @@
// @vitest-environment happy-dom
-///
+import '@testing-library/jest-dom/vitest';
import { fireEvent, render } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
diff --git a/code/addons/a11y/src/components/A11yContext.test.tsx b/code/addons/a11y/src/components/A11yContext.test.tsx
index f6ee6c5a969e..9eeb1763611e 100644
--- a/code/addons/a11y/src/components/A11yContext.test.tsx
+++ b/code/addons/a11y/src/components/A11yContext.test.tsx
@@ -1,4 +1,5 @@
// @vitest-environment happy-dom
+import '@testing-library/jest-dom/vitest';
import { act, cleanup, render } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
diff --git a/code/addons/links/src/react/components/link.test.tsx b/code/addons/links/src/react/components/link.test.tsx
index 8bd2e3bbcd01..e343dae703f4 100644
--- a/code/addons/links/src/react/components/link.test.tsx
+++ b/code/addons/links/src/react/components/link.test.tsx
@@ -1,5 +1,5 @@
// @vitest-environment happy-dom
-///
+import '@testing-library/jest-dom/vitest';
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, it, vi } from 'vitest';
diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json
index 758738a67d9a..94841810d613 100644
--- a/code/addons/vitest/package.json
+++ b/code/addons/vitest/package.json
@@ -87,11 +87,12 @@
"@types/micromatch": "^4.0.0",
"@types/node": "^22.19.1",
"@types/semver": "^7.7.1",
- "@vitest/browser-playwright": "^4.1.6",
- "@vitest/runner": "^4.1.6",
+ "@vitest/browser-playwright": "^5.0.0",
+ "@vitest/istanbul-lib-report": "^1.0.1",
"empathic": "^2.0.0",
"es-toolkit": "^1.43.0",
"istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.2.0",
"micromatch": "^4.0.8",
"pathe": "^1.1.2",
"picocolors": "^1.1.0",
@@ -104,14 +105,13 @@
"tree-kill": "^1.2.2",
"ts-dedent": "^2.2.0",
"typescript": "^6.0.3",
- "vitest": "^4.1.6"
+ "vitest": "^5.0.0"
},
"peerDependencies": {
- "@vitest/browser": "^3.0.0 || ^4.0.0 || ^5.0.0",
+ "@vitest/browser": "^4.0.0 || ^5.0.0",
"@vitest/browser-playwright": "^4.0.0 || ^5.0.0",
- "@vitest/runner": "^3.0.0 || ^4.0.0 || ^5.0.0",
"storybook": "workspace:^",
- "vitest": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ "vitest": "^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -120,9 +120,6 @@
"@vitest/browser-playwright": {
"optional": true
},
- "@vitest/runner": {
- "optional": true
- },
"vitest": {
"optional": true
}
diff --git a/code/addons/vitest/src/node/coverage-reporter.test.ts b/code/addons/vitest/src/node/coverage-reporter.test.ts
new file mode 100644
index 000000000000..2aa17e5b4086
--- /dev/null
+++ b/code/addons/vitest/src/node/coverage-reporter.test.ts
@@ -0,0 +1,81 @@
+import { execFile } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import { promisify } from 'node:util';
+
+import { expect, it, vi } from 'vitest';
+
+import type { ReportNode } from 'istanbul-lib-report';
+
+import StorybookCoverageReporter from './coverage-reporter.ts';
+import type { TestManager } from './test-manager.ts';
+
+it.each([
+ ['Vitest 3/4', "require('istanbul-reports').create"],
+ ['Vitest 5', "(await import('@vitest/istanbul-lib-report')).createAsync"],
+])('loads the built coverage reporter through the %s loader', async (_version, loader) => {
+ await expect(
+ promisify(execFile)(
+ process.execPath,
+ [
+ '--input-type=module',
+ '--eval',
+ `
+ import { strict as assert } from 'node:assert';
+ import { createRequire } from 'node:module';
+ const require = createRequire(import.meta.url);
+ const summaries = [];
+ const reporter = await (${loader})(
+ '@storybook/addon-vitest/internal/coverage-reporter',
+ {
+ testManager: {
+ onCoverageCollected: (summary) => summaries.push(summary),
+ vitestManager: { vitest: { config: { coverage: { watermarks: { statements: [50, 90] } } } } },
+ },
+ }
+ );
+ reporter.onSummary({
+ isRoot: () => true,
+ getCoverageSummary: () => ({ data: { statements: { pct: 87.4 } } }),
+ });
+ assert.deepEqual(summaries, [{ percentage: 87, status: 'warning' }]);
+ `,
+ ],
+ { cwd: fileURLToPath(new URL('../../', import.meta.url)) }
+ )
+ ).resolves.toMatchObject({ stdout: '' });
+});
+
+it.each([
+ { percentage: 49, watermarks: [50, 90], expected: { percentage: 49, status: 'negative' } },
+ { percentage: 50, watermarks: [50, 90], expected: { percentage: 50, status: 'warning' } },
+ { percentage: 89.5, watermarks: [50, 90], expected: { percentage: 90, status: 'positive' } },
+ { percentage: 79.4, watermarks: undefined, expected: { percentage: 79, status: 'warning' } },
+ { percentage: 80, watermarks: [], expected: { percentage: 80, status: 'positive' } },
+ { percentage: 60, watermarks: [60], expected: { percentage: 60, status: 'warning' } },
+])(
+ 'reports $expected for $percentage with watermarks $watermarks',
+ ({ percentage, watermarks, expected }) => {
+ const onCoverageCollected = vi.fn();
+ const testManager = {
+ onCoverageCollected,
+ vitestManager: {
+ vitest: { config: { coverage: { watermarks: { statements: watermarks } } } },
+ },
+ } as unknown as TestManager;
+ const reporter = new StorybookCoverageReporter({ testManager });
+ reporter.onSummary({
+ isRoot: () => true,
+ getCoverageSummary: () => ({ data: { statements: { pct: percentage } } }),
+ } as unknown as ReportNode);
+ expect(onCoverageCollected).toHaveBeenCalledExactlyOnceWith(expected);
+ }
+);
+
+it('ignores nested coverage summaries', () => {
+ const onCoverageCollected = vi.fn();
+ const reporter = new StorybookCoverageReporter({
+ testManager: { onCoverageCollected } as unknown as TestManager,
+ });
+ reporter.onSummary({ isRoot: () => false } as ReportNode);
+ expect(onCoverageCollected).not.toHaveBeenCalled();
+});
diff --git a/code/addons/vitest/src/node/coverage-reporter.ts b/code/addons/vitest/src/node/coverage-reporter.ts
index 118d1730c59f..efe2ea43b3ca 100644
--- a/code/addons/vitest/src/node/coverage-reporter.ts
+++ b/code/addons/vitest/src/node/coverage-reporter.ts
@@ -1,5 +1,3 @@
-import type { ResolvedCoverageOptions } from 'vitest/node';
-
import type { ReportNode, Visitor } from 'istanbul-lib-report';
import { ReportBase } from 'istanbul-lib-report';
@@ -8,18 +6,14 @@ import type { TestManager } from './test-manager.ts';
export type StorybookCoverageReporterOptions = {
testManager: TestManager;
- coverageOptions: ResolvedCoverageOptions | undefined;
};
class StorybookCoverageReporter extends ReportBase implements Partial {
#testManager: StorybookCoverageReporterOptions['testManager'];
- #coverageOptions: StorybookCoverageReporterOptions['coverageOptions'];
-
constructor(opts: StorybookCoverageReporterOptions) {
super();
this.#testManager = opts.testManager;
- this.#coverageOptions = opts.coverageOptions;
}
onSummary(node: ReportNode) {
@@ -32,7 +26,7 @@ class StorybookCoverageReporter extends ReportBase implements Partial {
// Fallback to Vitest's default watermarks https://vitest.dev/config/#coverage-watermarks
const [lowWatermark = 50, highWatermark = 80] =
- this.#coverageOptions?.watermarks?.statements ?? [];
+ this.#testManager.vitestManager.vitest?.config.coverage.watermarks?.statements ?? [];
const coverageSummary: StoreState['currentRun']['coverageSummary'] = {
percentage,
@@ -47,10 +41,7 @@ class StorybookCoverageReporter extends ReportBase implements Partial {
}
}
-/**
- * This is exported weirdly because the coverage tool loading this uses `Cont = require(name)`. So
- * it doesn't support a default export, it has to be the "root" export as CJS
- *
- * @see https://nodejs.org/docs/latest-v20.x/api/modules.html#loading-ecmascript-modules-using-require
- */
+export default StorybookCoverageReporter;
+
+// Vitest 3 and 4 load custom coverage reporters with require(name).
export { StorybookCoverageReporter as 'module.exports' };
diff --git a/code/addons/vitest/src/node/reporter.ts b/code/addons/vitest/src/node/reporter.ts
index 493541170323..d729d987e289 100644
--- a/code/addons/vitest/src/node/reporter.ts
+++ b/code/addons/vitest/src/node/reporter.ts
@@ -1,8 +1,6 @@
-import type { SerializedError } from 'vitest';
-import type { TestCase, TestModule, Vitest } from 'vitest/node';
-import { type Reporter } from 'vitest/reporters';
+import type { SerializedError, TaskMeta } from 'vitest';
+import type { Reporter, TestCase, TestModule, Vitest } from 'vitest/node';
-import type { TaskMeta } from '@vitest/runner';
import type { Report } from 'storybook/preview-api';
import type { VitestError } from '../types.ts';
diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts
index 7c2a61fcd4a7..8915d7d3dcfd 100644
--- a/code/addons/vitest/src/node/test-manager.test.ts
+++ b/code/addons/vitest/src/node/test-manager.test.ts
@@ -25,6 +25,7 @@ import { DOUBLE_SPACES } from './vitest-manager.ts';
const setTestNamePattern = vi.hoisted(() => vi.fn());
const vitest = vi.hoisted(() => ({
projects: [{}],
+ standalone: vi.fn(),
init: vi.fn(),
close: vi.fn(),
onCancel: vi.fn(),
@@ -52,10 +53,14 @@ const vitest = vi.hoisted(() => ({
},
}));
+const mockVitestVersion = vi.hoisted(() => ({ value: '4.1.6' }));
const mockCreateVitest = vi.fn();
vi.mock('vitest/node', () => ({
createVitest: mockCreateVitest,
+ get version() {
+ return mockVitestVersion.value;
+ },
}));
// Use the mock function directly
@@ -63,6 +68,7 @@ const createVitest = mockCreateVitest;
beforeEach(() => {
vi.clearAllMocks();
+ mockVitestVersion.value = '4.1.6';
mockStore.setState(() => storeOptions.initialState);
vitest.projects = [{}];
vitest.config.coverage.enabled = false;
@@ -194,6 +200,28 @@ const options: TestManagerOptions = {
};
describe('TestManager', () => {
+ it('initializes Vitest without running tests', async () => {
+ await TestManager.start(options);
+ expect(vitest.standalone).toHaveBeenCalledOnce();
+ expect(vitest.init).not.toHaveBeenCalled();
+ expect(vitest.runTestSpecifications).not.toHaveBeenCalled();
+ });
+
+ it.each(['3.2.4', '4.0.0'])(
+ 'initializes Vitest %s without standalone or running tests',
+ async (version) => {
+ mockVitestVersion.value = version;
+ const { standalone, ...legacyVitest } = vitest;
+ createVitest.mockResolvedValue(legacyVitest);
+
+ await TestManager.start(options);
+
+ expect(legacyVitest.init).toHaveBeenCalledOnce();
+ expect(standalone).not.toHaveBeenCalled();
+ expect(legacyVitest.runTestSpecifications).not.toHaveBeenCalled();
+ }
+ );
+
it('should create a vitest instance', async () => {
new TestManager(options);
await vi.waitFor(() => {
@@ -490,7 +518,11 @@ describe('TestManager', () => {
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^One$`));
});
- it('should trigger a single story test', async () => {
+ it.each([
+ ['4.1.6', ' '],
+ ['5.0.0', ' > '],
+ ])('should trigger a single story test with Vitest %s', async (version, separator) => {
+ mockVitestVersion.value = version;
vitest.globTestSpecifications.mockImplementation(() => tests);
const testManager = await startWithIndex();
@@ -503,7 +535,7 @@ describe('TestManager', () => {
});
// regex should be Parent Story Name + Test Name
expect(setTestNamePattern).toHaveBeenCalledWith(
- new RegExp(`^Parent story${DOUBLE_SPACES} Test name$`)
+ new RegExp(`^Parent story${DOUBLE_SPACES}${separator}Test name$`)
);
});
diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts
index c58b0ce2023d..95411ddbe37b 100644
--- a/code/addons/vitest/src/node/vitest-manager.ts
+++ b/code/addons/vitest/src/node/vitest-manager.ts
@@ -1,12 +1,6 @@
import { existsSync, readFileSync } from 'node:fs';
-import type {
- CoverageOptions,
- ResolvedCoverageOptions,
- TestProject,
- TestSpecification,
- Vitest,
-} from 'vitest/node';
+import type { CoverageOptions, TestProject, TestSpecification, Vitest } from 'vitest/node';
import { getProjectRoot, resolvePathInStorybookCache } from 'storybook/internal/common';
import { Tag } from 'storybook/internal/core-server';
@@ -44,6 +38,8 @@ const getTestName = (name: string) => `${name}${DOUBLE_SPACES}`;
export class VitestManager {
vitest: Vitest | null = null;
+ private testNameSeparator = ' ';
+
vitestStartupCounter = 0;
vitestRestartPromise: Promise | null = null;
@@ -53,13 +49,13 @@ export class VitestManager {
constructor(private testManager: TestManager) {}
async startVitest({ coverage }: { coverage: boolean }) {
- const { createVitest } = await import('vitest/node');
+ const { createVitest, version } = await import('vitest/node');
+ this.testNameSeparator = Number.parseInt(version, 10) >= 5 ? ' > ' : ' ';
const storybookCoverageReporter: [string, StorybookCoverageReporterOptions] = [
'@storybook/addon-vitest/internal/coverage-reporter',
{
testManager: this.testManager,
- coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions | undefined,
},
];
const coverageOptions = (
@@ -159,7 +155,11 @@ export class VitestManager {
}
try {
- await this.vitest.init();
+ if (typeof this.vitest.standalone === 'function') {
+ await this.vitest.standalone();
+ } else {
+ await this.vitest.init();
+ }
} catch (e: any) {
let message = 'Failed to initialize Vitest';
const isV8 = e.message?.includes('@vitest/coverage-v8');
@@ -261,7 +261,7 @@ export class VitestManager {
throw new Error(`Parent story not found for story ${story.id}`);
}
- return `^${escapeRegExp(getTestName(parentStory.name))} ${escapeRegExp(story.name)}$`;
+ return `^${escapeRegExp(getTestName(parentStory.name))}${this.testNameSeparator}${escapeRegExp(story.name)}$`;
}
return `^${escapeRegExp(story.name)}$`;
diff --git a/code/addons/vitest/src/updateVitestFile.config.4.test.ts b/code/addons/vitest/src/updateVitestFile.config.4.test.ts
index 81b391e725f9..0bb9e516f876 100644
--- a/code/addons/vitest/src/updateVitestFile.config.4.test.ts
+++ b/code/addons/vitest/src/updateVitestFile.config.4.test.ts
@@ -1,6 +1,9 @@
-import { join } from 'node:path';
+import { mkdtemp, rm, writeFile } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join, resolve } from 'node:path';
import { describe, expect, it, vi } from 'vitest';
+import { createVitest, type Vitest } from 'vitest/node';
import * as babel from 'storybook/internal/babel';
@@ -20,6 +23,77 @@ vi.mock('../../../core/src/shared/utils/module', () => ({
}));
describe('updateConfigFile', () => {
+ it.each(['existing projects', 'single project'])(
+ 'preserves shared Vite configuration and isolates unit setup when adding to %s',
+ async (mode) => {
+ const setupFile = resolve('unit-only-setup.js');
+ const source = babel.babelParse(
+ await loadTemplate('vitest.config.4.template', { CONFIG_DIR: '.storybook' })
+ );
+ const target = babel.babelParse(`
+ export default defineConfig({
+ plugins: [{ name: 'shared-plugin' }],
+ resolve: { alias: { 'shared-alias': '/shared' } },
+ test: {
+ ${
+ mode === 'existing projects'
+ ? `projects: [{ extends: true, test: { name: 'unit', setupFiles: [${JSON.stringify(setupFile)}] } }]`
+ : `name: 'unit', setupFiles: [${JSON.stringify(setupFile)}]`
+ }
+ }
+ });
+ `);
+ expect(updateConfigFile(source, target)).toBe(true);
+ const declaration = target.program.body.find(
+ (node) => node.type === 'ExportDefaultDeclaration'
+ )!;
+ const directory = await mkdtemp(join(tmpdir(), 'storybook-vitest-config-'));
+ let vitest: Vitest | undefined;
+ try {
+ const configFile = join(directory, 'vitest.config.mjs');
+ await writeFile(
+ configFile,
+ `
+ import path from 'node:path';
+ import { playwright } from ${JSON.stringify(import.meta.resolve('@vitest/browser-playwright'))};
+ const dirname = ${JSON.stringify(process.cwd())};
+ const defineConfig = (value) => value;
+ const storybookTest = () => ({ name: 'storybook-test-probe' });
+ export default ${babel.generate(declaration.declaration).code};
+ `
+ );
+ vitest = await createVitest('test', {
+ config: configFile,
+ configLoader: 'native',
+ watch: false,
+ reporters: [],
+ });
+ const unit = vitest.projects.find((project) => project.name === 'unit')!;
+ const storybook = vitest.projects.find((project) => project.config.browser.enabled)!;
+ expect(unit.config.setupFiles).toEqual([setupFile]);
+ expect(unit.vite.config.plugins.map((plugin) => plugin.name)).toContain('shared-plugin');
+ expect(unit.vite.config.resolve.alias).toContainEqual({
+ find: 'shared-alias',
+ replacement: '/shared',
+ });
+ expect(storybook.config.setupFiles).toEqual([]);
+ expect(storybook.vite.config.plugins.map((plugin) => plugin.name)).toContain(
+ 'shared-plugin'
+ );
+ expect(storybook.vite.config.resolve.alias).toContainEqual({
+ find: 'shared-alias',
+ replacement: '/shared',
+ });
+ expect(storybook.vite.config.plugins.map((plugin) => plugin.name)).toContain(
+ 'storybook-test-probe'
+ );
+ } finally {
+ await vitest?.close();
+ await rm(directory, { recursive: true, force: true });
+ }
+ }
+ );
+
it('updates vite config file with existing projects', async () => {
const source = babel.babelParse(
await loadTemplate('vitest.config.4.template', {
diff --git a/code/addons/vitest/src/vitest-plugin/agent-telemetry-reporter.ts b/code/addons/vitest/src/vitest-plugin/agent-telemetry-reporter.ts
index b8092e16ff91..c0196d95d8b9 100644
--- a/code/addons/vitest/src/vitest-plugin/agent-telemetry-reporter.ts
+++ b/code/addons/vitest/src/vitest-plugin/agent-telemetry-reporter.ts
@@ -1,8 +1,6 @@
-import type { SerializedError } from 'vitest';
-import type { TestCase, TestModule, Vitest } from 'vitest/node';
-import type { Reporter } from 'vitest/reporters';
+import type { SerializedError, TaskMeta } from 'vitest';
+import type { Reporter, TestCase, TestModule, Vitest } from 'vitest/node';
-import type { TaskMeta } from '@vitest/runner';
import type { Report } from 'storybook/preview-api';
import { analyzeTestResults, toStoryTestResult } from 'storybook/internal/core-server';
import type { StoryTestResult } from 'storybook/internal/core-server';
diff --git a/code/core/src/actions/models/HandlerFunction.test-d.ts b/code/core/src/actions/models/HandlerFunction.test-d.ts
index 455eb8a43e3c..e535d81e8478 100644
--- a/code/core/src/actions/models/HandlerFunction.test-d.ts
+++ b/code/core/src/actions/models/HandlerFunction.test-d.ts
@@ -1,9 +1,11 @@
-import { expectTypeOf } from 'vitest';
+import { expectTypeOf, test } from 'vitest';
import type { HandlerFunction } from './HandlerFunction.ts';
-// Should be assignable to async callback props (the fixed case)
-expectTypeOf().toExtend<() => Promise>();
+test('is assignable to async callbacks', () => {
+ expectTypeOf().toExtend<() => Promise>();
+});
-// Should remain assignable to plain void callbacks
-expectTypeOf().toExtend<() => void>();
+test('is assignable to void callbacks', () => {
+ expectTypeOf().toExtend<() => void>();
+});
diff --git a/code/core/src/backgrounds/index.test-d.ts b/code/core/src/backgrounds/index.test-d.ts
index 48a291580cde..52f1170348bd 100644
--- a/code/core/src/backgrounds/index.test-d.ts
+++ b/code/core/src/backgrounds/index.test-d.ts
@@ -1,15 +1,17 @@
-import { expectTypeOf } from 'vitest';
+import { expectTypeOf, test } from 'vitest';
import { DEFAULT_BACKGROUNDS } from 'storybook/backgrounds';
import type { Background, BackgroundMap } from 'storybook/backgrounds';
-expectTypeOf().toEqualTypeOf>();
+test('accepts built-in and custom background maps', () => {
+ expectTypeOf().toEqualTypeOf>();
-expectTypeOf(DEFAULT_BACKGROUNDS).toEqualTypeOf();
+ expectTypeOf(DEFAULT_BACKGROUNDS).toEqualTypeOf();
-const customBackgrounds = {
- light: { name: 'Light', value: '#ffffff' },
- dark: { name: 'Dark', value: '#1a1a1a' },
-} satisfies BackgroundMap;
+ const customBackgrounds = {
+ light: { name: 'Light', value: '#ffffff' },
+ dark: { name: 'Dark', value: '#1a1a1a' },
+ } satisfies BackgroundMap;
-expectTypeOf(customBackgrounds.light).toExtend();
+ expectTypeOf(customBackgrounds.light).toExtend();
+});
diff --git a/code/core/src/cli/angular/helpers.ts b/code/core/src/cli/angular/helpers.ts
index 9a00c6be5b3c..3ef49d5513a0 100644
--- a/code/core/src/cli/angular/helpers.ts
+++ b/code/core/src/cli/angular/helpers.ts
@@ -16,8 +16,7 @@ import semver from 'semver';
export const ANGULAR_JSON_PATH = 'angular.json';
-/** Must stay inside the `>=2.7.3` peer range that `@storybook/angular-vite` declares. */
-export const ANALOG_VITE_PLUGIN_ANGULAR_VERSION = '^2.7.3';
+export const ANALOG_VITE_PLUGIN_ANGULAR_VERSION = '^2.7.5';
export const toDevkitVersion = (ngRange?: string | null): string | undefined => {
if (!ngRange) {
diff --git a/code/core/src/common/js-package-manager/PNPMProxy.catalog.test.ts b/code/core/src/common/js-package-manager/PNPMProxy.catalog.test.ts
index a3c50c8bfdb0..439f0bfd00d8 100644
--- a/code/core/src/common/js-package-manager/PNPMProxy.catalog.test.ts
+++ b/code/core/src/common/js-package-manager/PNPMProxy.catalog.test.ts
@@ -25,7 +25,8 @@ describe('PNPMProxy catalogs', () => {
beforeEach(() => {
// Restore the real fs so the proxy can be constructed, then redirect fs to memfs.
- vi.resetAllMocks();
+ vi.mocked(readFileSync).mockReset();
+ vi.mocked(writeFileSync).mockReset();
pnpmProxy = new PNPMProxy();
vol.reset();
vi.mocked(readFileSync).mockImplementation(memfs.readFileSync as typeof readFileSync);
diff --git a/code/core/src/common/utils/__snapshots__/formatter.test.ts.snap b/code/core/src/common/utils/__snapshots__/formatter.test.ts.snap
index 784281ec3075..bd0a9ddf1a0c 100644
--- a/code/core/src/common/utils/__snapshots__/formatter.test.ts.snap
+++ b/code/core/src/common/utils/__snapshots__/formatter.test.ts.snap
@@ -1,14 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`formatter > withPrettierConfig > prettier > formats content with prettier 1`] = `
-"
-import type { Meta, StoryObj } from '@storybook/nextjs'
+"import type { Meta, StoryObj } from '@storybook/nextjs';
import Component from './foo';
- const meta = {
- component: Component
- } satisfies Meta;
+const meta = {
+ component: Component,
+} satisfies Meta;
export default meta;
@@ -17,14 +16,13 @@ type Story = StoryObj;
`;
exports[`formatter > withoutPrettierConfigAndWithEditorConfig > prettier-v3 > formats content with prettier 1`] = `
-"
-import type { Meta, StoryObj } from '@storybook/nextjs'
+"import type { Meta, StoryObj } from "@storybook/nextjs";
-import Component from './foo';
+import Component from "./foo";
- const meta = {
- component: Component
- } satisfies Meta;
+const meta = {
+ component: Component,
+} satisfies Meta;
export default meta;
diff --git a/code/core/src/common/utils/formatter.test.ts b/code/core/src/common/utils/formatter.test.ts
index 4d03f2800631..a5e3c0eb7e6b 100644
--- a/code/core/src/common/utils/formatter.test.ts
+++ b/code/core/src/common/utils/formatter.test.ts
@@ -37,7 +37,7 @@ describe('formatter', () => {
const testPath = resolve(__dirname, '__tests-formatter__', 'withPrettierConfig');
describe('prettier', async () => {
- const prettierV3 = await import('prettier');
+ const prettierV3 = await vi.importActual('prettier');
it('formats content with prettier', async () => {
mockPrettier.format.mockImplementation(prettierV3.format);
@@ -57,7 +57,7 @@ describe('formatter', () => {
const testPath = resolve(__dirname, '__tests-formatter__', 'withoutPrettierConfig');
describe('prettier-v3', async () => {
- const prettierV3 = await import('prettier');
+ const prettierV3 = await vi.importActual('prettier');
it('formats content with prettier', async () => {
mockPrettier.format.mockImplementation(prettierV3.format);
@@ -77,7 +77,7 @@ describe('formatter', () => {
const testPath = resolve(__dirname, '__tests-formatter__', 'withoutEditorConfig');
describe('prettier-v3', async () => {
- const prettierV3 = await import('prettier');
+ const prettierV3 = await vi.importActual('prettier');
it('formats content with prettier', async () => {
mockPrettier.format.mockImplementation(prettierV3.format);
diff --git a/code/core/src/manager-api/tests/stories.test.ts b/code/core/src/manager-api/tests/stories.test.ts
index 56d8651a9cac..4c6d139c0701 100644
--- a/code/core/src/manager-api/tests/stories.test.ts
+++ b/code/core/src/manager-api/tests/stories.test.ts
@@ -44,6 +44,8 @@ const getEventMetadata = vi.mocked(getEventMetadataOriginal);
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+vi.mock('../stores/status.ts');
+
vi.mock('../lib/events.ts', () => ({
getEventMetadata: vi.fn(() => ({ sourceType: 'local' })),
}));
@@ -1655,7 +1657,6 @@ describe('stories API', () => {
});
it('can filter on status', async () => {
- vi.mock('../stores/status');
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
@@ -2015,7 +2016,6 @@ describe('stories API', () => {
describe('computeStatusFilterFn (via experimental_setFilter)', () => {
it('passes through all stories when both included and excluded are empty', async () => {
- vi.mock('../stores/status');
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
@@ -2028,7 +2028,6 @@ describe('stories API', () => {
});
it('applies OR logic within included status filters', async () => {
- vi.mock('../stores/status');
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
@@ -2063,7 +2062,6 @@ describe('stories API', () => {
});
it('re-applies active filters on status changes without re-registering the status filter', async () => {
- vi.mock('../stores/status');
fullStatusStore.unset();
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
@@ -2097,7 +2095,6 @@ describe('stories API', () => {
});
it('a stream of status updates triggers a bounded number of index rebuilds', async () => {
- vi.mock('../stores/status');
vi.useFakeTimers();
try {
fullStatusStore.unset();
@@ -2135,7 +2132,6 @@ describe('stories API', () => {
});
it('applies the last status update after a burst (trailing edge of the throttle)', async () => {
- vi.mock('../stores/status');
vi.useFakeTimers();
try {
fullStatusStore.unset();
@@ -2175,7 +2171,6 @@ describe('stories API', () => {
});
it('does not overlap status-driven index rebuilds when a rebuild outlasts the throttle', async () => {
- vi.mock('../stores/status');
vi.useFakeTimers();
try {
fullStatusStore.unset();
@@ -2235,7 +2230,6 @@ describe('stories API', () => {
});
it('keeps rebuilding the status-filtered index after a rebuild rejection', async () => {
- vi.mock('../stores/status');
vi.useFakeTimers();
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
try {
@@ -2287,7 +2281,6 @@ describe('stories API', () => {
});
it('applies exclude logic: story with excluded status is hidden', async () => {
- vi.mock('../stores/status');
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
@@ -2322,7 +2315,6 @@ describe('stories API', () => {
});
it('story with no statuses is hidden when included filters are active', async () => {
- vi.mock('../stores/status');
const moduleArgs = createMockModuleArgs({});
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
const { store } = moduleArgs;
diff --git a/code/core/src/preview-api/modules/preview-web/WebView.test.ts b/code/core/src/preview-api/modules/preview-web/WebView.test.ts
index 28393168f11f..9f0503e9ce3a 100644
--- a/code/core/src/preview-api/modules/preview-web/WebView.test.ts
+++ b/code/core/src/preview-api/modules/preview-web/WebView.test.ts
@@ -1,4 +1,5 @@
// @vitest-environment happy-dom
+import '@testing-library/jest-dom/vitest';
import { beforeEach, describe, expect, it } from 'vitest';
import { WebView } from './WebView.ts';
diff --git a/code/e2e-sandbox/addon-onboarding.spec.ts b/code/e2e-sandbox/addon-onboarding.spec.ts
index 3b7351b57dae..df7874261750 100644
--- a/code/e2e-sandbox/addon-onboarding.spec.ts
+++ b/code/e2e-sandbox/addon-onboarding.spec.ts
@@ -1,8 +1,4 @@
-import { readdir, rm } from 'node:fs/promises';
-import { homedir } from 'node:os';
-
import { expect, test } from '@playwright/test';
-import { join } from 'pathe';
import process from 'process';
import { SbPage, hasOnboardingFeature } from './util.ts';
@@ -11,30 +7,6 @@ const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME || '';
const type = process.env.STORYBOOK_TYPE || 'dev';
-async function clearChecklistCache() {
- const storybookCacheDir = join(
- process.env.STORYBOOK_SANDBOX_DIR!,
- 'node_modules',
- '.cache',
- 'storybook'
- );
- const storybookCacheEntries = await readdir(storybookCacheDir, { withFileTypes: true }).catch(
- () => []
- );
-
- // Storybook scopes cache entries by version, so remove the checklist for any installed version.
- await Promise.all(
- storybookCacheEntries
- .filter((entry) => entry.isDirectory())
- .map((entry) =>
- rm(join(storybookCacheDir, entry.name, 'default', 'checklist'), {
- recursive: true,
- force: true,
- })
- )
- );
-}
-
test.describe('addon-onboarding', () => {
test.skip(type === 'build', `Skipping addon tests for production Storybooks`);
test.skip(
@@ -46,14 +18,22 @@ test.describe('addon-onboarding', () => {
`Skipping ${templateName}, whose onboarding coverage is carried by vue3-vite/default-ts.`
);
test('the onboarding flow', async ({ page }) => {
- // eslint-disable-next-line playwright/no-conditional-in-test
- if (process.env.CI) {
- await rm(join(homedir(), '.storybook', 'settings.json'), { force: true });
- await clearChecklistCache();
- }
+ test.setTimeout(60_000);
- await page.goto(`${storybookUrl}/?path=/onboarding`);
const sbPage = new SbPage(page, expect);
+ await page.goto(`${storybookUrl}/?path=/story/example-button--primary`);
+ await sbPage.waitUntilLoaded();
+ await page.evaluate(async () => {
+ const { internal_checklistStore, internal_universalChecklistStore } = (
+ window as unknown as {
+ __STORYBOOK_API__: typeof import('storybook/manager-api');
+ }
+ ).__STORYBOOK_API__;
+ await internal_universalChecklistStore.readyPromise;
+ internal_checklistStore.reset('onboardingSurvey');
+ });
+
+ await page.goto(`${storybookUrl}/?path=/onboarding`);
await sbPage.waitUntilLoaded();
await expect(page.getByRole('heading', { name: 'Meet your new frontend' })).toBeVisible();
diff --git a/code/e2e-sandbox/util.ts b/code/e2e-sandbox/util.ts
index 281220858e05..6cf471b56d3b 100644
--- a/code/e2e-sandbox/util.ts
+++ b/code/e2e-sandbox/util.ts
@@ -159,9 +159,8 @@ export class SbPage {
async closeAnyPendingModal() {
const popover = this.page.locator('[role="dialog"]');
if (await popover.isVisible()) {
- await this.page.keyboard.press('Escape');
- await this.page.keyboard.press('Escape');
- await popover.waitFor({ state: 'hidden', timeout: 1000 });
+ await popover.press('Escape');
+ await this.expect(popover).toBeHidden();
}
}
diff --git a/code/frameworks/angular-vite/package.json b/code/frameworks/angular-vite/package.json
index 1166ac9f5fe2..70114368460b 100644
--- a/code/frameworks/angular-vite/package.json
+++ b/code/frameworks/angular-vite/package.json
@@ -70,7 +70,7 @@
"vite": ">=8.0.0"
},
"devDependencies": {
- "@analogjs/vite-plugin-angular": "^2.5.2",
+ "@analogjs/vite-plugin-angular": "^2.7.5",
"@angular-devkit/architect": "^0.2102.12",
"@angular-devkit/build-angular": "^21.2.12",
"@angular-devkit/core": "^21.2.12",
@@ -92,7 +92,7 @@
"zone.js": "^0.16.2"
},
"peerDependencies": {
- "@analogjs/vite-plugin-angular": ">=2.7.3",
+ "@analogjs/vite-plugin-angular": ">=2.7.5",
"@angular-devkit/architect": ">=0.2100.0 < 0.2300.0",
"@angular-devkit/core": ">=21.0.0 < 23.0.0",
"@angular/build": ">=21.0.0 < 23.0.0",
diff --git a/code/frameworks/angular-vite/src/preset.test.ts b/code/frameworks/angular-vite/src/preset.test.ts
index f06f5969e2b7..b432a16bfc90 100644
--- a/code/frameworks/angular-vite/src/preset.test.ts
+++ b/code/frameworks/angular-vite/src/preset.test.ts
@@ -91,6 +91,29 @@ const optionsWith = (
},
}) as unknown as Parameters[1];
+describe('Angular hash initialization', () => {
+ afterEach(() => {
+ vi.mocked(createRequire).mockRestore();
+ });
+
+ it.each(['21.2.22', '22.1.0', '22.2.0'])(
+ 'initializes hashing when required by Angular %s',
+ async (version) => {
+ const initializeHash = vi.fn().mockResolvedValue(undefined);
+ const angularBuildPath = resolve('/angular/build/package.json');
+ const angularRequire = vi.fn((path: string) =>
+ path === angularBuildPath ? { version } : { initializeHash }
+ );
+ Object.assign(angularRequire, { resolve: () => angularBuildPath });
+ vi.mocked(createRequire).mockReturnValue(angularRequire as unknown as NodeJS.Require);
+
+ await viteFinal({}, optionsWith({}, { experimentalDocgenServer: true }));
+
+ expect(initializeHash).toHaveBeenCalledTimes(version === '22.2.0' ? 1 : 0);
+ }
+ );
+});
+
function runConfig(stylePreprocessorOptions: Record | undefined) {
const options = {
configDir: resolve(WORKSPACE_ROOT, '.storybook'),
diff --git a/code/frameworks/angular-vite/src/preset.ts b/code/frameworks/angular-vite/src/preset.ts
index 271bb15b00fe..685f505b8014 100644
--- a/code/frameworks/angular-vite/src/preset.ts
+++ b/code/frameworks/angular-vite/src/preset.ts
@@ -106,6 +106,15 @@ export const viteFinal = async (config: UserConfig, options: Options & Standalon
const { mergeConfig, normalizePath } = await import('vite');
const { default: angular } = await import('@analogjs/vite-plugin-angular');
+ const angularRequire = createRequire(import.meta.resolve('@analogjs/vite-plugin-angular'));
+ const angularBuildPath = angularRequire.resolve('@angular/build/package.json');
+ const { version } = angularRequire(angularBuildPath) as { version: string };
+ const [major, minor = 0] = version.split('.').map(Number);
+ // Angular 22.2 requires hash initialization before Analog's JavaScript transformer runs.
+ if (major === 22 && minor >= 2) {
+ await angularRequire(join(dirname(angularBuildPath), 'src/utils/hash.js')).initializeHash();
+ }
+
const framework = await options.presets.apply('framework');
const frameworkOptions: FrameworkOptions | undefined =
typeof framework === 'string' ? undefined : framework.options;
diff --git a/code/lib/create-storybook/src/services/ProjectTypeService.test.ts b/code/lib/create-storybook/src/services/ProjectTypeService.test.ts
index 299a04db1203..eef4a418f58e 100644
--- a/code/lib/create-storybook/src/services/ProjectTypeService.test.ts
+++ b/code/lib/create-storybook/src/services/ProjectTypeService.test.ts
@@ -29,7 +29,6 @@ describe('ProjectTypeService', () => {
it('logs a helpful message when framework cannot be detected', async () => {
const service = new ProjectTypeService(pm);
const options = { html: false } as unknown as CommandOptions;
- // @ts-expect-error accessing private for test
vi.spyOn(service, 'detectProjectType').mockResolvedValue(ProjectType.UNDETECTED);
await expect(service.autoDetectProjectType(options)).rejects.toThrowError(
diff --git a/code/package.json b/code/package.json
index b7d25b631c0b..b57ca8d9b322 100644
--- a/code/package.json
+++ b/code/package.json
@@ -93,10 +93,10 @@
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^4.3.2",
"@vitejs/plugin-vue": "^4.4.0",
- "@vitest/browser": "^4.1.6",
- "@vitest/browser-playwright": "^4.1.6",
- "@vitest/coverage-istanbul": "^4.1.6",
- "@vitest/coverage-v8": "^4.1.6",
+ "@vitest/browser": "^5.0.0",
+ "@vitest/browser-playwright": "^5.0.0",
+ "@vitest/coverage-istanbul": "^5.0.0",
+ "@vitest/coverage-v8": "^5.0.0",
"chromatic": "^18.0.1",
"create-storybook": "workspace:*",
"danger": "^13.0.4",
@@ -123,7 +123,7 @@
"uuid": "^11.1.0",
"vite": "^7.0.4",
"vite-plugin-inspect": "^11.0.0",
- "vitest": "^4.1.6",
+ "vitest": "^5.0.0",
"wait-on": "^8.0.3"
},
"devDependencies": {
diff --git a/code/renderers/react/src/componentManifest/generator.react-docgen-typescript.test.ts b/code/renderers/react/src/componentManifest/generator.react-docgen-typescript.test.ts
index fa05cfec2701..14d8bbe41814 100644
--- a/code/renderers/react/src/componentManifest/generator.react-docgen-typescript.test.ts
+++ b/code/renderers/react/src/componentManifest/generator.react-docgen-typescript.test.ts
@@ -18,34 +18,38 @@ type ManifestComponentWithReactDocgenTypescript = {
reactDocgenTypescript?: ComponentDocWithExportName;
};
-test.sequential('manifests uses the referenced app tsconfig for react-docgen-typescript in Vite-style projects', async () => {
- invalidateCache();
- invalidateParser();
-
- const tempDir = createTempProject();
- const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
-
- try {
- const result = await manifests(undefined, createManifestOptions());
- const button = result?.components?.components?.['example-button'] as
- | ManifestComponentWithReactDocgenTypescript
- | undefined;
-
- expect(button?.error).toBeUndefined();
- expect(button?.reactDocgenTypescript?.displayName).toBe('Button');
- expect(button?.reactDocgenTypescript?.props.label).toMatchObject({
- required: true,
- });
- expect(button?.reactDocgenTypescript?.props.primary).toMatchObject({
- required: false,
- });
- } finally {
- cwdSpy.mockRestore();
+test(
+ 'manifests uses the referenced app tsconfig for react-docgen-typescript in Vite-style projects',
+ { concurrent: false },
+ async () => {
invalidateCache();
invalidateParser();
- rmSync(tempDir, { recursive: true, force: true });
+
+ const tempDir = createTempProject();
+ const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
+
+ try {
+ const result = await manifests(undefined, createManifestOptions());
+ const button = result?.components?.components?.['example-button'] as
+ | ManifestComponentWithReactDocgenTypescript
+ | undefined;
+
+ expect(button?.error).toBeUndefined();
+ expect(button?.reactDocgenTypescript?.displayName).toBe('Button');
+ expect(button?.reactDocgenTypescript?.props.label).toMatchObject({
+ required: true,
+ });
+ expect(button?.reactDocgenTypescript?.props.primary).toMatchObject({
+ required: false,
+ });
+ } finally {
+ cwdSpy.mockRestore();
+ invalidateCache();
+ invalidateParser();
+ rmSync(tempDir, { recursive: true, force: true });
+ }
}
-});
+);
function createManifestOptions() {
const manifestEntries: IndexEntry[] = [
diff --git a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts
index d6752601ba64..0957f7ccdf24 100644
--- a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts
+++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts
@@ -1,5 +1,5 @@
// @vitest-environment happy-dom
-///
+import '@testing-library/jest-dom/vitest';
import { cleanup, render, screen } from '@testing-library/vue';
import { afterEach, describe, expect, it, vi } from 'vitest';
diff --git a/docs/writing-tests/integrations/vitest-addon/index.mdx b/docs/writing-tests/integrations/vitest-addon/index.mdx
index 3c8341325a90..42fd584290b8 100644
--- a/docs/writing-tests/integrations/vitest-addon/index.mdx
+++ b/docs/writing-tests/integrations/vitest-addon/index.mdx
@@ -282,11 +282,17 @@ If your stories use assets in the public directory and you're not using the defa
### How do I isolate Storybook tests from others?
-Some projects might contain a `test` property in their Vite configuration. Because the Vitest configuration used by this plugin extends that Vite config, the `test` properties are merged. This lack of isolation can cause issues with your Storybook tests.
+The generated Storybook test project uses `extends: true` to share your root Vite configuration, including framework plugins and aliases. It also inherits root-level test settings, so unit-only setup files at the root can interfere with your stories.
-To isolate your Storybook tests from other tests, you need to move the `test` property from your Vite configuration to the Vitest configuration. The Vitest config used by the plugin can then safely extend your Vite config without merging the `test` property.
+Move unit-only settings, such as `setupFiles` and `environment`, into the unit-test project inside `test.projects`. Keep shared Vite plugins and aliases at the root. When the addon converts a single test configuration into projects, it moves project-specific test settings into the unit project automatically.
-Additionally, we recommend using a [test project](#example-configuration-files) if you're using Vitest ≥ 4.0, or a workspace for previous versions to define separate configurations for your Storybook tests and other tests. This ensures each can be run either in isolation or together, depending on your needs.
+If you need to isolate inherited project configuration, set `extends: false` on the Storybook project and explicitly configure its required framework plugins and aliases, either on that project or through Storybook's [`viteFinal`](../../../api/main-config/main-config-vite-final.mdx).
+
+Root `globalSetup` and root plugin hooks such as `config` still run even with `extends: false`. Move unit-only global setup into the unit-test project, and keep root plugin hooks safe for all projects.
+
+Coverage is a [global Vitest option](https://vitest.dev/guide/projects), so `extends: false` does not isolate it. When you run `vitest --project=storybook --coverage`, Vitest still uses the root `test.coverage` configuration.
+
+Use a [test project](#example-configuration-files) if you're using Vitest ≥ 4.0, or a workspace for previous versions, to define separate configurations for Storybook tests and other tests. This allows each suite to run independently or together.
### Why do we recommend browser mode?
diff --git a/package.json b/package.json
index aeaf54fdffa5..2c1479d026f4 100644
--- a/package.json
+++ b/package.json
@@ -88,7 +88,7 @@
"oxfmt": "^0.41.0",
"std-env": "^4.0.0",
"vite": "^7.0.4",
- "vitest": "^4.1.6"
+ "vitest": "^5.0.0"
},
"packageManager": "yarn@4.18.0"
}
diff --git a/scripts/package.json b/scripts/package.json
index 630297178197..1920b03513f4 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -76,7 +76,7 @@
"@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4",
"@types/window-size": "^1.1.4",
- "@vitest/coverage-v8": "^4.1.6",
+ "@vitest/coverage-v8": "^5.0.0",
"chromatic": "^18.0.1",
"citty": "^0.2.1",
"codecov": "^3.8.1",
@@ -151,7 +151,7 @@
"typescript": "^6.0.3",
"typescript-native": "npm:typescript@^7.0.2",
"uuid": "^9.0.1",
- "vitest": "^4.1.6",
+ "vitest": "^5.0.0",
"wait-on": "^8.0.3",
"window-size": "^1.1.1",
"yaml": "^2.8.1",
diff --git a/scripts/vitest-compatibility/README.md b/scripts/vitest-compatibility/README.md
new file mode 100644
index 000000000000..df0482ff31ba
--- /dev/null
+++ b/scripts/vitest-compatibility/README.md
@@ -0,0 +1,112 @@
+# Vitest addon runtime compatibility
+
+Run with the Node version in `.nvmrc`, npm, and the repository's Yarn installation.
+Compile the addon, framework, renderer, builder, and core before preparing a fixture:
+
+```sh
+yarn nx run-many -t compile --projects=addon-vitest,react-vite
+node scripts/vitest-compatibility/prepare.ts /tmp/storybook-vitest-5 5.0.1 react
+cd /tmp/storybook-vitest-5
+npx playwright install chromium
+node run.ts
+```
+
+Repeat with separate directories and versions `4.0.0` and `5.0.0`. Preparation packages
+local build output into tarballs and installs real, independent dependencies. It does
+not link the monorepo's node_modules. `--legacy-peer-deps` prevents npm from installing
+optional, unrelated Storybook peers; the required runtime peers are pinned explicitly.
+Keep the generated package-lock.json with the evidence when reproducing an installation.
+
+The harness starts the published addon child-process entry point, bridges real
+UniversalStores, and runs actual Chromium stories. It checks startup without a run,
+all stories, exact child selection containing regex punctuation, parent selection,
+coverage off/on/off, and automatic watch failure/recovery after component edits.
+Native V8 execution counts verify three `init()` calls on Vitest 4 or three
+`standalone()` calls on Vitest 5, including both coverage restarts. A separate CLI
+`vitest run --project=storybook --coverage` invocation verifies coverage output.
+
+The intentionally failing watch run must fail before recovery passes. Its diagnostic
+output is expected. Harness assertion failures produce a nonzero exit status.
+
+Artifacts stay in the fixture: environment.json, package-lock.json, results.json,
+lifecycle.json, native-coverage/, and coverage-cli/. Run browser checks outside an OS
+sandbox that prevents Chromium from launching. Do not treat a blocked launch as a
+product failure or a passing test.
+
+## Framework configuration migration
+
+Compile `vue3-vite` and `svelte-vite` before preparing the corresponding fixtures.
+From the repository root:
+
+```sh
+node scripts/vitest-compatibility/prepare.ts /tmp/storybook-vitest-vue 5.0.1 vue3
+node scripts/vitest-compatibility/framework-config.ts /tmp/storybook-vitest-vue vue3 existing
+cd /tmp/storybook-vitest-vue
+node node_modules/vitest/vitest.mjs run --project=storybook
+```
+
+Repeat with mode `single`, and with framework `svelte` in another directory. The
+configuration comes from the actual updater and template. Real Vue/Svelte compiler
+plugins and an alias live in shared configuration. Unit setup deliberately throws
+if it leaks into the Storybook project. Explicit dependency prebundling avoids
+cold-cache reloads interrupting the first browser run.
+
+## Storybook UI
+
+After a successful React `run.ts`, add `@storybook/addon-vitest` to the fixture's
+`.storybook/main.js` addons array. Start the UI from the fixture directory:
+
+```sh
+node node_modules/storybook/dist/bin/dispatcher.js dev --ci --port 6026
+```
+
+In another terminal in that directory, run `node ui.ts http://localhost:6026`.
+It selects a nested story, starts a test run through the UI, enables watch mode,
+edits the component to fail, restores it, and verifies displayed failure/recovery.
+It saves a Playwright trace and screenshots. Exact child/parent filtering is asserted
+by `run.ts` against the real backend's completed run results.
+
+## Coverage and mutation measurements
+
+Collect source coverage from the repository root:
+
+```sh
+yarn --cwd code/addons/vitest vitest run --coverage --coverage.provider=v8 \
+ --coverage.include=src/node/vitest-manager.ts \
+ --coverage.include=src/node/coverage-reporter.ts \
+ --coverage.include=src/vitest-plugin/index.ts \
+ --coverage.reporter=json --coverage.reportsDirectory=/tmp/vitest-source-coverage
+node scripts/vitest-compatibility/quality.ts /tmp/vitest-source-coverage/coverage-final.json /tmp/vitest-quality.json
+```
+
+The report states its complexity and coverage conventions. It measures complete
+changed methods, including their pre-existing branches. Source coverage here is
+from unit tests; native/browser evidence is reported separately, not silently merged.
+The score formula is `complexity² × (1 − coverage)³ + complexity`.
+
+Install pinned mutation tooling outside the repository, then run from the repository:
+
+```sh
+npm install --prefix /tmp/storybook-mutation-tools --no-audit --no-fund @stryker-mutator/core@10.0.0
+node scripts/vitest-compatibility/mutation.ts /tmp/storybook-mutation-tools /tmp/storybook-mutation-results
+```
+
+This uses the command runner with coverage analysis disabled. Stryker's Vitest
+runner currently constructs space-separated test-name filters, which do not select
+Vitest 5 nested tests correctly. The command runner executes a fresh focused suite
+for every mutant. The sandbox build compiles the mutated coverage reporter, so the
+native loader tests also exercise mutations. Sandboxes stay outside the repository
+to avoid NX discovering duplicate projects. Keep timeouts separate from killed
+mutants when interpreting the JSON report; survivors are not waived or hidden.
+
+After successful unmodified compatibility runs, execute explicit built-artifact
+regression probes:
+
+```sh
+node scripts/vitest-compatibility/regressions.ts /tmp/storybook-vitest-4 /tmp/storybook-vitest-5 /tmp/storybook-regressions
+```
+
+These revert individual compatibility changes in disposable fixture packages and
+require the expected failure. They restore each package in `finally`. They supplement
+the generated mutation report; they do not establish a zero-survivor score for whole
+functions or substitute for a complete base-branch run.
diff --git a/scripts/vitest-compatibility/framework-config.ts b/scripts/vitest-compatibility/framework-config.ts
new file mode 100644
index 000000000000..e06363e8b421
--- /dev/null
+++ b/scripts/vitest-compatibility/framework-config.ts
@@ -0,0 +1,54 @@
+import assert from 'node:assert/strict';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { join, resolve } from 'node:path';
+
+import { babelParse, generate } from 'storybook/internal/babel';
+
+import { updateConfigFile } from '../../code/addons/vitest/src/updateVitestFile.ts';
+
+const [directory, framework, mode = 'single'] = process.argv.slice(2);
+assert(directory && ['vue3', 'svelte'].includes(framework));
+const plugin = framework === 'vue3' ? '@vitejs/plugin-vue' : '@sveltejs/vite-plugin-svelte';
+const extension = framework === 'vue3' ? 'vue' : 'svelte';
+await mkdir(join(directory, '.storybook'), { recursive: true });
+await writeFile(
+ join(directory, '.storybook/main.js'),
+ `export default { stories: ['../*.stories.js'], addons: [], framework: '@storybook/${framework}-vite', core: { disableTelemetry: true } };`
+);
+await writeFile(join(directory, '.storybook/preview.js'), 'export default {};');
+await writeFile(join(directory, 'label.js'), "export default 'Ready';");
+await writeFile(
+ join(directory, `Button.${extension}`),
+ framework === 'vue3'
+ ? ``
+ : ``
+);
+await writeFile(
+ join(directory, 'Button.stories.js'),
+ `import Button from './Button.${extension}';
+import { expect } from 'storybook/test';
+export default { title: 'Framework', component: Button };
+export const Primary = { play: async ({ canvas }) => { await expect(canvas.getByRole('button')).toHaveTextContent('Ready'); } };`
+);
+await writeFile(
+ join(directory, 'unit-only-setup.js'),
+ `throw new Error('Unit setup leaked into the Storybook project');`
+);
+const target = babelParse(`import { defineConfig } from 'vitest/config';
+import ${framework === 'vue3' ? 'frameworkPlugin' : '{ svelte as frameworkPlugin }'} from '${plugin}';
+export default defineConfig({
+ plugins: [frameworkPlugin()],
+ optimizeDeps: { include: ${JSON.stringify(framework === 'vue3' ? ['vue'] : ['svelte', 'svelte/internal/client', 'svelte/internal/flags/legacy'])} },
+ resolve: { alias: { 'fixture-label': ${JSON.stringify(resolve(directory, 'label.js'))} } },
+ test: { ${mode === 'existing' ? "projects: [{ extends: true, test: { name: 'unit', setupFiles: ['./unit-only-setup.js'] } }]" : "name: 'unit', setupFiles: ['./unit-only-setup.js']"} }
+});`);
+const source = babelParse(
+ (
+ await readFile(
+ new URL('../../code/addons/vitest/templates/vitest.config.4.template.ts', import.meta.url),
+ 'utf8'
+ )
+ ).replace('CONFIG_DIR', '.storybook')
+);
+assert(updateConfigFile(source, target));
+await writeFile(join(directory, 'vitest.config.mjs'), generate(target).code);
diff --git a/scripts/vitest-compatibility/mutation-build.ts b/scripts/vitest-compatibility/mutation-build.ts
new file mode 100644
index 000000000000..ea3b958790df
--- /dev/null
+++ b/scripts/vitest-compatibility/mutation-build.ts
@@ -0,0 +1,25 @@
+import assert from 'node:assert/strict';
+import { symlink } from 'node:fs/promises';
+import { resolve } from 'node:path';
+
+assert(process.env.STORYBOOK_MUTATION_ROOT);
+await symlink(
+ resolve(process.env.STORYBOOK_MUTATION_ROOT, 'node_modules'),
+ resolve('node_modules'),
+ 'junction'
+);
+const { build } = await import('esbuild');
+await symlink(
+ resolve(process.env.STORYBOOK_MUTATION_ROOT, 'code/core/dist'),
+ resolve('code/core/dist'),
+ 'dir'
+);
+await build({
+ entryPoints: ['code/addons/vitest/src/node/coverage-reporter.ts'],
+ outfile: 'code/addons/vitest/dist/node/coverage-reporter.js',
+ bundle: true,
+ packages: 'external',
+ format: 'esm',
+ platform: 'node',
+ target: 'node22',
+});
diff --git a/scripts/vitest-compatibility/mutation.ts b/scripts/vitest-compatibility/mutation.ts
new file mode 100644
index 000000000000..78c49d93a288
--- /dev/null
+++ b/scripts/vitest-compatibility/mutation.ts
@@ -0,0 +1,91 @@
+import assert from 'node:assert/strict';
+import { spawn } from 'node:child_process';
+import { once } from 'node:events';
+import { createWriteStream } from 'node:fs';
+import { finished } from 'node:stream/promises';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { resolve, join } from 'node:path';
+
+import ts from 'typescript';
+
+const [toolsDirectory, outputDirectory] = process.argv.slice(2);
+assert(
+ toolsDirectory && outputDirectory,
+ 'Usage: node scripts/vitest-compatibility/mutation.ts TOOLS_DIRECTORY OUTPUT_DIRECTORY'
+);
+const root = resolve(import.meta.dirname, '../..');
+const output = resolve(outputDirectory);
+await mkdir(output, { recursive: true });
+const targets = [
+ ['code/addons/vitest/src/node/vitest-manager.ts', ['startVitest', 'buildStoryTestNamePattern']],
+ ['code/addons/vitest/src/vitest-plugin/index.ts', ['config']],
+ ['code/addons/vitest/src/node/coverage-reporter.ts', ['constructor', 'onSummary']],
+] as const;
+const mutate: string[] = [];
+for (const [file, names] of targets) {
+ const source = ts.createSourceFile(
+ file,
+ await readFile(join(root, file), 'utf8'),
+ ts.ScriptTarget.Latest,
+ true
+ );
+ function visit(node: ts.Node) {
+ const name = ts.isConstructorDeclaration(node)
+ ? 'constructor'
+ : ts.isMethodDeclaration(node)
+ ? node.name.getText(source)
+ : undefined;
+ if (name && (names as readonly string[]).includes(name)) {
+ const start = source.getLineAndCharacterOfPosition(node.getStart());
+ const end = source.getLineAndCharacterOfPosition(node.end);
+ mutate.push(`${file}:${start.line + 1}:${start.character}-${end.line + 1}:${end.character}`);
+ }
+ ts.forEachChild(node, visit);
+ }
+ visit(source);
+}
+const configuration = {
+ testRunner: 'command',
+ plugins: [] as string[],
+ coverageAnalysis: 'off',
+ concurrency: 2,
+ timeoutMS: 10000,
+ commandRunner: {
+ command:
+ 'node node_modules/vitest/vitest.mjs run --config scripts/vitest-compatibility/vitest.mutation.config.ts',
+ },
+ buildCommand: 'node scripts/vitest-compatibility/mutation-build.ts',
+ mutate,
+ reporters: ['clear-text', 'json'],
+ jsonReporter: { fileName: join(output, 'mutation.json') },
+ tempDirName: join(output, 'sandboxes'),
+ ignorePatterns: [
+ '**/dist/**',
+ '**/node_modules/**',
+ '.git/**',
+ '.yarn/**',
+ '.nx/**',
+ 'docs/**',
+ 'test-storybooks/**',
+ ],
+};
+const configFile = join(output, 'stryker.config.json');
+await writeFile(configFile, JSON.stringify(configuration, null, 2));
+const log = createWriteStream(join(output, 'mutation.log'));
+const child = spawn(
+ process.execPath,
+ [resolve(toolsDirectory, 'node_modules/@stryker-mutator/core/bin/stryker.js'), 'run', configFile],
+ {
+ cwd: root,
+ env: { ...process.env, STORYBOOK_MUTATION_ROOT: root },
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 30 * 60 * 1000,
+ }
+);
+child.stdout.pipe(log, { end: false });
+child.stderr.pipe(log, { end: false });
+const [code] = await once(child, 'close');
+log.end();
+await finished(log);
+assert.equal(code, 0, `Mutation run failed; see ${join(output, 'mutation.log')}`);
+process.stdout.write(`Mutation report: ${join(output, 'mutation.json')}\n`);
diff --git a/scripts/vitest-compatibility/prepare.ts b/scripts/vitest-compatibility/prepare.ts
new file mode 100644
index 000000000000..49028708cd4c
--- /dev/null
+++ b/scripts/vitest-compatibility/prepare.ts
@@ -0,0 +1,118 @@
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import { cp, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
+import { resolve, join } from 'node:path';
+
+const root = resolve(import.meta.dirname, '../..');
+assert.equal(process.versions.node, (await readFile(join(root, '.nvmrc'), 'utf8')).trim());
+const [destination, version = '5.0.1', framework = 'react'] = process.argv.slice(2);
+assert(
+ destination,
+ 'Usage: node scripts/vitest-compatibility/prepare.ts DIRECTORY VERSION FRAMEWORK'
+);
+assert(['react', 'vue3', 'svelte'].includes(framework));
+assert(/^[45]\./.test(version), 'Supported compatibility versions are Vitest 4 and 5');
+const directory = resolve(destination);
+type Manifest = {
+ name: string;
+ version: string;
+ dependencies?: Record;
+ devDependencies?: Record;
+ peerDependencies?: Record;
+ optionalDependencies?: Record;
+ scripts?: Record;
+};
+const packages = new Map();
+for (const category of ['.', 'addons', 'builders', 'frameworks', 'renderers', 'lib']) {
+ const parent = join(root, 'code', category);
+ for (const entry of await readdir(parent, { withFileTypes: true })) {
+ if (!entry.isDirectory()) continue;
+ const path = join(parent, entry.name);
+ try {
+ const manifest = JSON.parse(await readFile(join(path, 'package.json'), 'utf8'));
+ packages.set(manifest.name, { path, manifest });
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
+ }
+ }
+}
+await mkdir(directory, { recursive: true });
+const dependencies: Record = {};
+async function pack(name: string) {
+ if (dependencies[name]) return;
+ const pkg = packages.get(name);
+ assert(pkg, `Missing local package ${name}`);
+ const staging = join(directory, '.packages', name.replaceAll('/', '-'));
+ await mkdir(staging, { recursive: true });
+ dependencies[name] = '';
+ const manifest = structuredClone(pkg.manifest);
+ delete manifest.devDependencies;
+ delete manifest.scripts;
+ for (const field of ['dependencies', 'peerDependencies', 'optionalDependencies'] as const) {
+ const packageDependencies = manifest[field];
+ if (!packageDependencies) continue;
+ for (const [dependency, range] of Object.entries(packageDependencies)) {
+ if (String(range).startsWith('workspace:')) {
+ packageDependencies[dependency] = packages.get(dependency)!.manifest.version;
+ await pack(dependency);
+ }
+ }
+ }
+ await writeFile(join(staging, 'package.json'), JSON.stringify(manifest, null, 2));
+ await cp(join(pkg.path, 'dist'), join(staging, 'dist'), { recursive: true });
+ for (const entry of await readdir(pkg.path)) {
+ if (
+ ['templates', 'assets', 'static', 'input'].includes(entry) ||
+ /\.(mjs|cjs|js|ts)$/.test(entry)
+ ) {
+ await cp(join(pkg.path, entry), join(staging, entry), { recursive: true });
+ }
+ }
+ const result = JSON.parse(
+ execFileSync('npm', ['pack', '--ignore-scripts', '--json'], { cwd: staging, encoding: 'utf8' })
+ );
+ dependencies[name] = `file:${join(staging, result[0].filename)}`;
+}
+await pack('@storybook/addon-vitest');
+await pack(`@storybook/${framework}-vite`);
+Object.assign(dependencies, {
+ vitest: version,
+ '@vitest/coverage-v8': version,
+ '@vitest/browser-playwright': version,
+ playwright: '1.58.2',
+ vite: version.startsWith('5.') ? '7.3.1' : '6.3.6',
+ typescript: '6.0.3',
+ ...(framework === 'react'
+ ? { react: '18.3.1', 'react-dom': '18.3.1', '@vitejs/plugin-react': '4.7.0' }
+ : {}),
+ ...(framework === 'vue3' ? { vue: '3.5.30', '@vitejs/plugin-vue': '6.0.5' } : {}),
+ ...(framework === 'svelte' ? { svelte: '5.53.12', '@sveltejs/vite-plugin-svelte': '6.2.4' } : {}),
+});
+await writeFile(
+ join(directory, 'package.json'),
+ JSON.stringify(
+ { name: 'storybook-vitest-compatibility', private: true, type: 'module', dependencies },
+ null,
+ 2
+ )
+);
+execFileSync('npm', ['install', '--legacy-peer-deps', '--no-audit', '--no-fund'], {
+ cwd: directory,
+ stdio: 'inherit',
+});
+await cp(join(import.meta.dirname, 'run.ts'), join(directory, 'run.ts'));
+await cp(join(import.meta.dirname, 'ui.ts'), join(directory, 'ui.ts'));
+await writeFile(
+ join(directory, 'environment.json'),
+ JSON.stringify(
+ {
+ sha: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(),
+ node: process.versions.node,
+ version,
+ framework,
+ dependencies,
+ },
+ null,
+ 2
+ )
+);
diff --git a/scripts/vitest-compatibility/quality.ts b/scripts/vitest-compatibility/quality.ts
new file mode 100644
index 000000000000..aeb185ca076f
--- /dev/null
+++ b/scripts/vitest-compatibility/quality.ts
@@ -0,0 +1,118 @@
+import assert from 'node:assert/strict';
+import { readFile, writeFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+
+import ts from 'typescript';
+
+const [coverageFile, output = 'vitest-quality.json'] = process.argv.slice(2);
+assert(
+ coverageFile,
+ 'Usage: node scripts/vitest-compatibility/quality.ts COVERAGE_JSON OUTPUT_JSON'
+);
+const coverage: Record<
+ string,
+ {
+ statementMap: Record;
+ s: Record;
+ }
+> = JSON.parse(await readFile(coverageFile, 'utf8'));
+const targets = [
+ ['code/addons/vitest/src/node/vitest-manager.ts', 'startVitest'],
+ ['code/addons/vitest/src/node/vitest-manager.ts', 'buildStoryTestNamePattern'],
+ ['code/addons/vitest/src/vitest-plugin/index.ts', 'config'],
+ ['code/addons/vitest/src/node/coverage-reporter.ts', 'constructor'],
+ ['code/addons/vitest/src/node/coverage-reporter.ts', 'onSummary'],
+];
+const report = [];
+for (const [file, name] of targets) {
+ const source = ts.createSourceFile(
+ file,
+ await readFile(file, 'utf8'),
+ ts.ScriptTarget.Latest,
+ true
+ );
+ const matches: (ts.MethodDeclaration | ts.ConstructorDeclaration)[] = [];
+ function find(node: ts.Node) {
+ if (
+ (ts.isMethodDeclaration(node) && node.name.getText(source) === name) ||
+ (ts.isConstructorDeclaration(node) && name === 'constructor')
+ )
+ matches.push(node);
+ ts.forEachChild(node, find);
+ }
+ find(source);
+ assert.equal(matches.length, 1, `${file}:${name}`);
+ const method = matches[0];
+ let complexity = 1;
+ function count(node: ts.Node) {
+ if (node !== method && ts.isFunctionLike(node)) return;
+ if (
+ ts.isIfStatement(node) ||
+ ts.isConditionalExpression(node) ||
+ ts.isForStatement(node) ||
+ ts.isForOfStatement(node) ||
+ ts.isForInStatement(node) ||
+ ts.isWhileStatement(node) ||
+ ts.isDoStatement(node) ||
+ ts.isCatchClause(node) ||
+ ts.isCaseClause(node)
+ )
+ complexity++;
+ if (
+ ts.isBinaryExpression(node) &&
+ [
+ ts.SyntaxKind.AmpersandAmpersandToken,
+ ts.SyntaxKind.BarBarToken,
+ ts.SyntaxKind.QuestionQuestionToken,
+ ts.SyntaxKind.AmpersandAmpersandEqualsToken,
+ ts.SyntaxKind.BarBarEqualsToken,
+ ts.SyntaxKind.QuestionQuestionEqualsToken,
+ ].includes(node.operatorToken.kind)
+ )
+ complexity++;
+ if (
+ (ts.isPropertyAccessExpression(node) ||
+ ts.isElementAccessExpression(node) ||
+ ts.isCallExpression(node)) &&
+ node.questionDotToken
+ )
+ complexity++;
+ ts.forEachChild(node, count);
+ }
+ count(method);
+ const start = source.getLineAndCharacterOfPosition(method.getStart()).line + 1;
+ const end = source.getLineAndCharacterOfPosition(method.end).line + 1;
+ const data = coverage[resolve(file)];
+ const statements =
+ data &&
+ Object.entries(data.statementMap).filter(
+ ([, location]) => location.start.line >= start && location.end.line <= end
+ );
+ const covered = statements?.filter(([id]) => data.s[id] > 0).length;
+ const ratio = statements?.length ? covered / statements.length : undefined;
+ report.push({
+ file,
+ name,
+ start,
+ end,
+ complexity,
+ coveredStatements: covered,
+ totalStatements: statements?.length,
+ coverage: ratio,
+ crap: ratio === undefined ? null : complexity ** 2 * (1 - ratio) ** 3 + complexity,
+ complexityFloorPasses: complexity < 25,
+ });
+}
+await writeFile(
+ output,
+ JSON.stringify(
+ {
+ convention:
+ 'Cyclomatic decisions including short-circuit and optional-chain branches, excluding nested functions; Istanbul statement coverage over the complete method; CRAP = complexity^2 * (1 - coverage)^3 + complexity.',
+ report,
+ },
+ null,
+ 2
+ )
+);
+process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
diff --git a/scripts/vitest-compatibility/regressions.ts b/scripts/vitest-compatibility/regressions.ts
new file mode 100644
index 000000000000..b9b40eddcef8
--- /dev/null
+++ b/scripts/vitest-compatibility/regressions.ts
@@ -0,0 +1,77 @@
+import assert from 'node:assert/strict';
+import { execFile } from 'node:child_process';
+import { mkdir, readFile, writeFile } from 'node:fs/promises';
+import { join, resolve } from 'node:path';
+import { promisify } from 'node:util';
+
+const [vitest4, vitest5, outputDirectory] = process.argv.slice(2);
+assert(
+ vitest4 && vitest5 && outputDirectory,
+ 'Usage: node scripts/vitest-compatibility/regressions.ts VITEST4_DIRECTORY VITEST5_DIRECTORY OUTPUT_DIRECTORY'
+);
+const output = resolve(outputDirectory);
+await mkdir(output, { recursive: true });
+const probes = [
+ {
+ name: 'vitest5-init',
+ directory: vitest5,
+ file: 'node/vitest.js',
+ pattern:
+ /typeof this\.vitest\.standalone == "function" \? await this\.vitest\.standalone\(\) : await this\.vitest\.init\(\)/,
+ replacement: 'await this.vitest.init()',
+ expected: /Deprecated Vitest.init/,
+ },
+ {
+ name: 'vitest5-separator',
+ directory: vitest5,
+ file: 'node/vitest.js',
+ pattern: /this\.testNameSeparator = Number\.parseInt\(version, 10\) >= 5 \? " > " : " "/,
+ replacement: 'this.testNameSeparator = " "',
+ expected: /0 !== 1/,
+ },
+ {
+ name: 'vitest5-default-export',
+ directory: vitest5,
+ file: 'node/coverage-reporter.js',
+ pattern: / coverage_reporter_default as default,\n/,
+ replacement: '',
+ expected: /not a constructor|default export|Could not load|Failed to run tests/,
+ },
+ {
+ name: 'vitest4-commonjs-export',
+ directory: vitest4,
+ file: 'node/coverage-reporter.js',
+ pattern: / StorybookCoverageReporter as "module.exports"/,
+ replacement: '',
+ expected: /not a constructor|Failed to run tests/,
+ },
+];
+const results = [];
+for (const probe of probes) {
+ const file = join(probe.directory, 'node_modules/@storybook/addon-vitest/dist', probe.file);
+ const original = await readFile(file, 'utf8');
+ assert(probe.pattern.test(original), `Build pattern missing: ${probe.name}`);
+ try {
+ await writeFile(file, original.replace(probe.pattern, probe.replacement));
+ let failure: { code?: string | number; stdout?: string; stderr?: string } | undefined;
+ try {
+ await promisify(execFile)(process.execPath, ['run.ts'], {
+ cwd: probe.directory,
+ timeout: 120000,
+ maxBuffer: 10 * 1024 * 1024,
+ });
+ } catch (error) {
+ failure = error as typeof failure;
+ }
+ assert(failure, `Regression survived: ${probe.name}`);
+ assert.equal(failure.code, 1, `Unexpected process failure: ${probe.name}`);
+ const log = (failure.stdout ?? '') + (failure.stderr ?? '');
+ await writeFile(join(output, `${probe.name}.log`), log);
+ assert.match(log, probe.expected, `Unexpected failure: ${probe.name}`);
+ results.push({ name: probe.name, status: 'caught', exitCode: failure.code });
+ process.stdout.write(`PASS regression probe: ${probe.name}\n`);
+ } finally {
+ await writeFile(file, original);
+ }
+}
+await writeFile(join(output, 'regressions.json'), JSON.stringify(results, null, 2));
diff --git a/scripts/vitest-compatibility/run.ts b/scripts/vitest-compatibility/run.ts
new file mode 100644
index 000000000000..3db508fc39a3
--- /dev/null
+++ b/scripts/vitest-compatibility/run.ts
@@ -0,0 +1,284 @@
+import assert from 'node:assert/strict';
+import { once } from 'node:events';
+import { promisify } from 'node:util';
+import type { CurrentRun, StoreState, StoreEvent } from '../../code/addons/vitest/src/types.ts';
+import { fork, execFile, type ChildProcess } from 'node:child_process';
+import { mkdir, readFile, writeFile, readdir, rm } from 'node:fs/promises';
+import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { ChannelEvent } from 'storybook/internal/channels';
+import {
+ internal_UniversalStore as UniversalStore,
+ prepareHeadlessUniversalStores,
+ internal_universalStatusStore,
+ internal_universalTestProviderStore,
+} from 'storybook/internal/core-server';
+import { loadCsf } from 'storybook/internal/csf-tools';
+import { storeOptions, STORY_INDEX_CHANNEL_EVENT_NAME } from '@storybook/addon-vitest/constants';
+
+const environment = JSON.parse(await readFile('environment.json', 'utf8'));
+const version = JSON.parse(await readFile('node_modules/vitest/package.json', 'utf8')).version;
+assert.equal(version, environment.version);
+assert(/^[45]\./.test(version), 'Supported compatibility versions are Vitest 4 and 5');
+assert.equal(process.versions.node, '22.22.3');
+const configDir = resolve('.storybook');
+await mkdir(configDir, { recursive: true });
+await writeFile(
+ '.storybook/main.js',
+ `export default { stories: ['../*.stories.jsx'], addons: [], features: { experimentalTestSyntax: true }, framework: '@storybook/react-vite', core: { disableTelemetry: true } };`
+);
+await writeFile(
+ '.storybook/preview.js',
+ `import { __definePreview } from '@storybook/react'; export default __definePreview({});`
+);
+await writeFile(
+ 'Button.jsx',
+ `import React from 'react';
+export const unused = () => {
+ return 'Uncovered';
+};
+export const Button = () => ;`
+);
+const stories = `import { Button } from './Button.jsx';
+import preview from './.storybook/preview.js';
+import { expect } from 'storybook/test';
+const meta = preview.meta({ title: 'Compatibility', component: Button });
+export const Primary = meta.story({ name: 'Primary (button)' });
+Primary.test('renders [ready]', async ({ canvas }) => { await expect(canvas.getByRole('button')).toHaveTextContent('Ready'); });
+Primary.test('renders [ready] again', async ({ canvas }) => { await expect(canvas.getByRole('button')).toHaveTextContent('Ready'); });
+export const PrimaryMobile = meta.story({ name: 'Primary (button) Mobile', play: async ({ canvas }) => { await expect(canvas.getByRole('button')).toHaveTextContent('Ready'); } });
+`;
+await writeFile('Button.stories.jsx', stories);
+await writeFile(
+ 'vitest.config.mjs',
+ `import { defineConfig } from 'vitest/config';
+import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
+import { playwright } from '@vitest/browser-playwright';
+export default defineConfig({ test: { coverage: { provider: 'v8', include: ['Button.jsx'], watermarks: { statements: [90, 100] }, reporter: ['json-summary'], reportsDirectory: './coverage-cli' }, projects: [{ extends: true, optimizeDeps: { include: ['@storybook/react'] }, plugins: [storybookTest({ configDir: ${JSON.stringify(configDir)} })], test: { name: 'storybook', browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: 'chromium' }] } } }] } });`
+);
+let child: ChildProcess | undefined;
+const channel = prepareHeadlessUniversalStores();
+const inputs = loadCsf(stories, {
+ fileName: resolve('Button.stories.jsx'),
+ makeTitle: (title) => title,
+}).parse().indexInputs;
+const entries = Object.fromEntries(
+ inputs.map((input) => {
+ assert(input.type === 'story' && input.__id && input.name && input.title);
+ return [
+ input.__id,
+ {
+ id: input.__id,
+ name: input.name,
+ title: input.title,
+ type: 'story' as const,
+ subtype: input.subtype,
+ parent: input.parent,
+ importPath: './Button.stories.jsx',
+ tags: ['test'],
+ },
+ ];
+ })
+);
+const selectedInput = inputs.find((input) => input.name === 'renders [ready]');
+assert(selectedInput?.__id);
+const childId = selectedInput.__id;
+const store = UniversalStore.create({
+ ...storeOptions,
+ leader: true,
+ initialState: storeOptions.initialState,
+});
+function bridge(
+ source: UniversalStore,
+ id: string
+) {
+ source.subscribe((event, eventInfo) =>
+ child?.send({ type: `UNIVERSAL_STORE:${id}`, args: [{ event, eventInfo }], from: 'server' })
+ );
+}
+bridge(store, 'storybook/test');
+bridge(internal_universalStatusStore, 'storybook/status');
+bridge(internal_universalTestProviderStore, 'storybook/test-provider');
+const events: StoreEvent[] = [];
+store.subscribe((event) => {
+ if (event.type === 'FATAL_ERROR' || event.type === 'TEST_RUN_COMPLETED') events.push(event);
+});
+function waitFor(predicate: () => boolean, label: string) {
+ return new Promise((accept, reject) => {
+ const timer = setTimeout(() => {
+ clearInterval(poll);
+ reject(new Error(`Timeout: ${label}\n${JSON.stringify(events.slice(-5))}`));
+ }, 90000);
+ const poll = setInterval(() => {
+ const fatal = events.find((event) => event.type === 'FATAL_ERROR');
+ if (fatal || predicate()) {
+ clearTimeout(timer);
+ clearInterval(poll);
+ if (fatal) reject(new Error(JSON.stringify(fatal)));
+ else accept();
+ }
+ }, 50);
+ });
+}
+let ready = false;
+await rm('native-coverage', { recursive: true, force: true });
+try {
+ child = fork(fileURLToPath(import.meta.resolve('@storybook/addon-vitest/vitest')), [], {
+ env: {
+ ...process.env,
+ VITEST: 'true',
+ TEST: 'true',
+ VITEST_CHILD_PROCESS: 'true',
+ STORYBOOK_DISABLE_TELEMETRY: '1',
+ STORYBOOK_CONFIG_DIR: configDir,
+ NODE_V8_COVERAGE: resolve('native-coverage'),
+ },
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
+ });
+ child.on('message', (message) => {
+ const event = message as ChannelEvent & {
+ payload: Extract['payload'];
+ };
+ if (event.type === 'ready') ready = true;
+ else if (event.type === 'uncaught-error')
+ events.push({ type: 'FATAL_ERROR', payload: event.payload });
+ else channel.receive(event);
+ });
+ await waitFor(() => ready, 'backend startup');
+ child.send({ type: STORY_INDEX_CHANNEL_EVENT_NAME, args: [{ v: 5, entries }] });
+ assert.equal(events.filter((event) => event.type === 'TEST_RUN_COMPLETED').length, 0);
+ const results: CurrentRun[] = [];
+ async function run(storyIds?: string[], coverage = false) {
+ const before = events.length;
+ store.send({
+ type: 'TRIGGER_RUN',
+ payload: { storyIds, triggeredBy: 'run-all', configOverride: { coverage, a11y: false } },
+ });
+ await waitFor(
+ () => events.slice(before).some((event) => event.type === 'TEST_RUN_COMPLETED'),
+ 'test completion'
+ );
+ const result = events
+ .slice(before)
+ .find((event) => event.type === 'TEST_RUN_COMPLETED')!.payload;
+ assert.deepEqual(result.unhandledErrors, []);
+ assert.equal(result.componentTestCount.error, 0);
+ results.push(result);
+ return result;
+ }
+ assert.equal((await run()).componentTestCount.success, 4);
+ const selected = await run([childId]);
+ assert.equal(selected.componentTestCount.success, 1);
+ assert.equal(selected.totalTestCount, 1);
+ assert.equal((await run(['compatibility--primary'])).componentTestCount.success, 3);
+ const covered = await run(undefined, true);
+ assert(covered.coverageSummary);
+ assert(covered.coverageSummary.percentage > 0 && covered.coverageSummary.percentage < 90);
+ assert.equal(covered.coverageSummary.status, 'negative');
+ assert.equal((await run()).coverageSummary, undefined);
+ store.setState((state) => ({ ...state, watching: true }));
+ const before = events.length;
+ await writeFile(
+ 'Button.jsx',
+ `import React from 'react';
+export const unused = () => {
+ return 'Uncovered';
+};
+export const Button = () => ;`
+ );
+ await waitFor(
+ () =>
+ events
+ .slice(before)
+ .some(
+ (event) =>
+ event.type === 'TEST_RUN_COMPLETED' && event.payload.componentTestCount.error === 3
+ ),
+ 'watch failure'
+ );
+ const failed = events.length;
+ await writeFile(
+ 'Button.jsx',
+ `import React from 'react';
+export const unused = () => {
+ return 'Uncovered';
+};
+export const Button = () => ;`
+ );
+ await waitFor(
+ () =>
+ events
+ .slice(failed)
+ .some(
+ (event) =>
+ event.type === 'TEST_RUN_COMPLETED' && event.payload.componentTestCount.success === 4
+ ),
+ 'watch recovery'
+ );
+ await writeFile('results.json', JSON.stringify({ environment, results, events }, null, 2));
+ process.stdout.write(
+ `PASS Vitest ${version}: startup, filtering, coverage off/on/off, watch pass/fail/pass\n`
+ );
+} finally {
+ if (child) {
+ const exited = once(child, 'exit');
+ child.kill('SIGTERM');
+ await exited;
+ }
+ channel.removeAllListeners();
+}
+
+type ScriptCoverage = {
+ url: string;
+ functions: {
+ functionName: string;
+ ranges: { startOffset: number; endOffset: number; count: number }[];
+ }[];
+};
+const nativeCoverage: { result: ScriptCoverage[] }[] = await Promise.all(
+ (await readdir('native-coverage')).map(async (file) =>
+ JSON.parse(await readFile(resolve('native-coverage', file), 'utf8'))
+ )
+);
+const api = version.startsWith('5.') ? 'standalone' : 'init';
+if (version.startsWith('5.')) {
+ for (const script of nativeCoverage
+ .flatMap((coverage) => coverage.result)
+ .filter((script) => script.url.includes('/vitest/dist/'))) {
+ const source = await readFile(fileURLToPath(script.url), 'utf8');
+ for (const fn of script.functions.filter((fn) => fn.functionName === 'init')) {
+ const range = fn.ranges[0];
+ if (
+ source.slice(range.startOffset, range.endOffset).includes('`vitest.init()` is deprecated.')
+ ) {
+ assert.equal(range.count, 0, 'Deprecated Vitest.init() was called');
+ }
+ }
+ }
+}
+
+const executions = nativeCoverage
+ .flatMap((coverage) => coverage.result)
+ .filter((script) => script.url.includes('/vitest/dist/'))
+ .flatMap((script) =>
+ script.functions
+ .filter((fn) => fn.functionName === api && fn.ranges[0].count === 3)
+ .map((fn) => ({ url: script.url, ...fn }))
+ );
+assert.equal(
+ executions.length,
+ 1,
+ `Expected three real ${api} calls: initial startup and two coverage restarts`
+);
+await writeFile('lifecycle.json', JSON.stringify(executions, null, 2));
+await promisify(execFile)(
+ process.execPath,
+ ['node_modules/vitest/vitest.mjs', 'run', '--project=storybook', '--coverage'],
+ { timeout: 90000, maxBuffer: 10 * 1024 * 1024 }
+);
+const summary = JSON.parse(await readFile('coverage-cli/coverage-summary.json', 'utf8'));
+assert(summary.total.statements.total > 0);
+const results = JSON.parse(await readFile('results.json', 'utf8'));
+const coveredRun = results.results.find((run: CurrentRun) => run.coverageSummary);
+assert.equal(Math.round(summary.total.statements.pct), coveredRun.coverageSummary.percentage);
+process.stdout.write(`PASS Vitest ${version}: CLI coverage and real ${api} execution evidence\n`);
diff --git a/scripts/vitest-compatibility/ui.ts b/scripts/vitest-compatibility/ui.ts
new file mode 100644
index 000000000000..bd9a1c2e433d
--- /dev/null
+++ b/scripts/vitest-compatibility/ui.ts
@@ -0,0 +1,55 @@
+import assert from 'node:assert/strict';
+import { readFile, writeFile } from 'node:fs/promises';
+
+import { chromium } from 'playwright';
+
+const url = process.argv[2] ?? 'http://localhost:6026';
+const component = await readFile('Button.jsx', 'utf8');
+const browser = await chromium.launch();
+const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
+await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
+const page = await context.newPage();
+page.setDefaultTimeout(60000);
+try {
+ await page.goto(`${url}/?path=/story/compatibility--primary:renders-ready`);
+ await page.getByRole('button', { name: 'Expand testing module' }).click();
+ const watch = page.getByRole('switch', { name: 'Watch mode', exact: true });
+ await watch.waitFor();
+ if ((await watch.getAttribute('aria-checked')) === 'true') await watch.click();
+ const start = page.getByRole('button', { name: 'Start test run', exact: true });
+ await start.click();
+ await page.getByRole('button', { name: 'Stop test run', exact: true }).waitFor();
+ await start.waitFor();
+ await page.getByRole('button', { name: 'Component tests passed', exact: true }).waitFor();
+ assert.match(await page.locator('body').innerText(), /Ran 4 tests/);
+ await watch.click();
+ await page.getByRole('switch', { name: 'Watch mode', exact: true, checked: true }).waitFor();
+ await writeFile('Button.jsx', component.replace('Ready', 'Changed'));
+ await page.getByRole('button', { name: /^Component tests failed/ }).waitFor();
+ await start.waitFor();
+ await page.screenshot({ path: 'ui-watch-failure.png' });
+ await writeFile('Button.jsx', component);
+ await page.getByRole('button', { name: 'Component tests passed', exact: true }).waitFor();
+ await start.waitFor();
+ await watch.click();
+ await page.getByRole('switch', { name: 'Watch mode', exact: true, checked: false }).waitFor();
+ await page.screenshot({ path: 'ui-watch-recovery.png' });
+ process.stdout.write('PASS UI: story selection, run, watch failure and recovery\n');
+} catch (error) {
+ await writeFile(
+ 'ui-failure.txt',
+ (await page.locator('body').innerText()) +
+ '\n' +
+ JSON.stringify(
+ await page
+ .getByRole('button')
+ .evaluateAll((elements) => elements.map((element) => element.getAttribute('aria-label')))
+ )
+ );
+ await page.screenshot({ path: 'ui-failure.png' });
+ throw error;
+} finally {
+ await writeFile('Button.jsx', component);
+ await context.tracing.stop({ path: 'ui-trace.zip' });
+ await browser.close();
+}
diff --git a/scripts/vitest-compatibility/vitest.mutation.config.ts b/scripts/vitest-compatibility/vitest.mutation.config.ts
new file mode 100644
index 000000000000..724212cba5cd
--- /dev/null
+++ b/scripts/vitest-compatibility/vitest.mutation.config.ts
@@ -0,0 +1,20 @@
+import { resolve } from 'node:path';
+
+import { defineConfig, mergeConfig } from 'vitest/config';
+
+import { vitestCommonConfig } from '../../code/vitest.shared.ts';
+
+export default mergeConfig(
+ vitestCommonConfig,
+ defineConfig({
+ root: resolve(import.meta.dirname, '../../code/addons/vitest'),
+ test: {
+ passWithNoTests: false,
+ include: [
+ 'src/node/test-manager.test.ts',
+ 'src/node/coverage-reporter.test.ts',
+ 'src/vitest-plugin/vitest-root.test.ts',
+ ],
+ },
+ })
+);
diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts
index d734f4b8c022..7b5bfdbf700f 100644
--- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts
+++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts
@@ -3,7 +3,7 @@ import path from "node:path";
import { expect, test, type Page } from "@playwright/test";
-import { SbPage } from "../../../../code/e2e-sandbox/util";
+import { SbPage } from "../../../../code/e2e-sandbox/util.ts";
const STORYBOOK_URL = "http://localhost:6006";
const TEST_STORY_PATH = path.resolve(
@@ -119,13 +119,12 @@ test.describe("component testing", () => {
await clearStatusesButton.click({ force: true });
}
- const watchModeToggle = page.getByRole("switch", { name: "Watch mode" });
- if (
- (await watchModeToggle.isVisible()) &&
- (await watchModeToggle.getAttribute("aria-checked")) === "true"
- ) {
- await watchModeToggle.click({ force: true });
+ const watchModeToggle = page.getByRole("switch", { name: /^Watch mode/ });
+ await expect(watchModeToggle).toBeEnabled({ timeout: 30000 });
+ if (await watchModeToggle.isChecked()) {
+ await watchModeToggle.click();
}
+ await expect(watchModeToggle).not.toBeChecked();
const configs = [
page.getByRole("checkbox", { name: "Coverage" }),
diff --git a/yarn.lock b/yarn.lock
index ca8d1bad6387..bae4b1764251 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -119,15 +119,14 @@ __metadata:
languageName: node
linkType: hard
-"@analogjs/vite-plugin-angular@npm:^2.5.2":
- version: 2.6.1
- resolution: "@analogjs/vite-plugin-angular@npm:2.6.1"
+"@analogjs/vite-plugin-angular@npm:^2.5.2, @analogjs/vite-plugin-angular@npm:^2.7.5":
+ version: 2.7.5
+ resolution: "@analogjs/vite-plugin-angular@npm:2.7.5"
dependencies:
magic-string: "npm:^0.30.21"
obug: "npm:^2.1.1"
oxc-parser: "npm:^0.121.0"
tinyglobby: "npm:^0.2.14"
- ts-morph: "npm:^21.0.0"
peerDependencies:
"@angular-devkit/build-angular": ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0
"@angular/build": ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0
@@ -139,7 +138,7 @@ __metadata:
optional: true
vite:
optional: true
- checksum: 10c0/cc4f509feb48fa68a52eff4313cade2e091662705146807feca4a2b114d6452f0f59854eb18d0c4cd6ae398df7aaad3f05cf90e98678be00fa6cb7a4e40b21d3
+ checksum: 10c0/530622ca948fba34b1862429b25a06a326f2d492818ebb91f297165cdc9b14454aa34247732a7890cff3a2c03e58080f296a84637aa04451909f93bcbede518d
languageName: node
linkType: hard
@@ -687,6 +686,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/code-frame@npm:^8.0.0, @babel/code-frame@npm:^8.0.6":
+ version: 8.0.6
+ resolution: "@babel/code-frame@npm:8.0.6"
+ dependencies:
+ "@babel/helper-validator-identifier": "npm:^8.0.6"
+ js-tokens: "npm:^10.0.0"
+ checksum: 10c0/ae3022ce1e41a48e367712f32c51736624992c1b87be17714ce0970063935c511bccbea4e005db02cc4da87370b608b5861378fcab86a78c9764fa3bd2b6959a
+ languageName: node
+ linkType: hard
+
"@babel/compat-data@npm:^7.28.6, @babel/compat-data@npm:^7.29.0, @babel/compat-data@npm:^7.29.7":
version: 7.29.7
resolution: "@babel/compat-data@npm:7.29.7"
@@ -694,6 +703,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/compat-data@npm:^8.0.5":
+ version: 8.0.5
+ resolution: "@babel/compat-data@npm:8.0.5"
+ checksum: 10c0/53931a4e15538655b2e720098afcc18405abfbc6b8f8df7d85320f3a6193145a3e5b1c87c61218f58fc4bd6c51cea6fa78092ee723e3fb12e089d89802a8a05a
+ languageName: node
+ linkType: hard
+
"@babel/core@npm:7.29.7, @babel/core@npm:^7.12.0, @babel/core@npm:^7.23.7, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.5, @babel/core@npm:^7.29.0, @babel/core@npm:^7.3.4, @babel/core@npm:^7.7.5":
version: 7.29.7
resolution: "@babel/core@npm:7.29.7"
@@ -717,6 +733,30 @@ __metadata:
languageName: node
linkType: hard
+"@babel/core@npm:^8.0.1":
+ version: 8.0.6
+ resolution: "@babel/core@npm:8.0.6"
+ dependencies:
+ "@babel/code-frame": "npm:^8.0.6"
+ "@babel/generator": "npm:^8.0.6"
+ "@babel/helper-compilation-targets": "npm:^8.0.6"
+ "@babel/helpers": "npm:^8.0.5"
+ "@babel/parser": "npm:^8.0.6"
+ "@babel/template": "npm:^8.0.0"
+ "@babel/traverse": "npm:^8.0.6"
+ "@babel/types": "npm:^8.0.6"
+ "@types/gensync": "npm:^1.0.5"
+ convert-source-map: "npm:^2.0.0"
+ empathic: "npm:^2.0.1"
+ gensync: "npm:^1.0.0-beta.2"
+ import-meta-resolve: "npm:^4.2.0"
+ json5: "npm:^2.2.3"
+ obug: "npm:^2.1.1"
+ verkit: "npm:^0.3.2"
+ checksum: 10c0/8198276c898f37d4a1a4e68ea79dc66948638a91a5a4f6b6b23b4e077d96ed80eb79df2810f23ec55f4223738993f8ddabc3bc90076eb63946e76580671ecc0b
+ languageName: node
+ linkType: hard
+
"@babel/generator@npm:7.29.1":
version: 7.29.1
resolution: "@babel/generator@npm:7.29.1"
@@ -743,17 +783,17 @@ __metadata:
languageName: node
linkType: hard
-"@babel/generator@npm:^8.0.0":
- version: 8.0.0
- resolution: "@babel/generator@npm:8.0.0"
+"@babel/generator@npm:^8.0.0, @babel/generator@npm:^8.0.6":
+ version: 8.0.6
+ resolution: "@babel/generator@npm:8.0.6"
dependencies:
- "@babel/parser": "npm:^8.0.0"
- "@babel/types": "npm:^8.0.0"
- "@jridgewell/gen-mapping": "npm:^0.3.12"
- "@jridgewell/trace-mapping": "npm:^0.3.28"
+ "@babel/parser": "npm:^8.0.6"
+ "@babel/types": "npm:^8.0.6"
+ "@jridgewell/gen-mapping": "npm:0.4.0-beta.0"
+ "@jridgewell/trace-mapping": "npm:^0.3.31"
"@types/jsesc": "npm:^2.5.0"
jsesc: "npm:^3.0.2"
- checksum: 10c0/2b2d171beaedcacca62164f0f39bc8962df5f9700a54c5515174276ea20b3a53933e9804e0b35c2be1059108a764447d99d0c5beb63ecbbf8c66035ca6c003c4
+ checksum: 10c0/ea3b078547815e7ee2a865c9618bbdeaca83961690f036791c05fbdd9a5dbfa0e7bd1dc36434cffba6c8be508297bad798ae47096551a6c38a7ff2e4d139729d
languageName: node
linkType: hard
@@ -788,6 +828,19 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-compilation-targets@npm:^8.0.6":
+ version: 8.0.6
+ resolution: "@babel/helper-compilation-targets@npm:8.0.6"
+ dependencies:
+ "@babel/compat-data": "npm:^8.0.5"
+ "@babel/helper-validator-option": "npm:^8.0.0"
+ browserslist: "npm:^4.24.0"
+ flru: "npm:1.0.2"
+ verkit: "npm:^0.3.2"
+ checksum: 10c0/959e4f528544aa16c3b490b9e20e89cc2f3ce641e88b818e00f509bab3fe11afb9a365e4ebf120590519bcef303386155f02d888b48eb031de71b19d0fdfef73
+ languageName: node
+ linkType: hard
+
"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.27.1, @babel/helper-create-class-features-plugin@npm:^7.29.7":
version: 7.29.7
resolution: "@babel/helper-create-class-features-plugin@npm:7.29.7"
@@ -948,10 +1001,10 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-validator-identifier@npm:^8.0.0":
- version: 8.0.4
- resolution: "@babel/helper-validator-identifier@npm:8.0.4"
- checksum: 10c0/9d3c639a42b70613016fda026487a3b8056df918cb40221a48ef666a3bd2574475e315ee509c552baef4bef55a627ee8ea530e9af5b376512ab248c37aeab336
+"@babel/helper-validator-identifier@npm:^8.0.0, @babel/helper-validator-identifier@npm:^8.0.6":
+ version: 8.0.6
+ resolution: "@babel/helper-validator-identifier@npm:8.0.6"
+ checksum: 10c0/9170fe52f3c330945b259cfb5786fb0e1fb4bdafd4deedcb0d3cbe300e993c8df9ca2307734bd3009220799f3053d206557f78d942df0f41e565304e19d2595c
languageName: node
linkType: hard
@@ -962,6 +1015,13 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-validator-option@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "@babel/helper-validator-option@npm:8.0.0"
+ checksum: 10c0/eb466b464479f75c15ba06f6c53862f10d90740350c9203b3d81c6f33d4193bf0a7b7b89de901f256fb639a1346475b48616734af89906d6b4fcf9a856d9f1f1
+ languageName: node
+ linkType: hard
+
"@babel/helper-wrap-function@npm:^7.29.7":
version: 7.29.7
resolution: "@babel/helper-wrap-function@npm:7.29.7"
@@ -983,6 +1043,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helpers@npm:^8.0.5":
+ version: 8.0.5
+ resolution: "@babel/helpers@npm:8.0.5"
+ dependencies:
+ "@babel/template": "npm:^8.0.0"
+ "@babel/types": "npm:^8.0.5"
+ checksum: 10c0/eb215dd47757fffebc5f690c721e77e541e2131ffa1e06b5f026a75a8b7726b30cd24e6d97fe0a30cbc7865417169f563e0f6c0af45597172fa05e44b2cee3d0
+ languageName: node
+ linkType: hard
+
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.6, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8, @babel/parser@npm:^7.4.5, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6":
version: 7.29.8
resolution: "@babel/parser@npm:7.29.8"
@@ -994,14 +1064,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^8.0.0":
- version: 8.0.4
- resolution: "@babel/parser@npm:8.0.4"
+"@babel/parser@npm:^8.0.0, @babel/parser@npm:^8.0.4, @babel/parser@npm:^8.0.6":
+ version: 8.0.6
+ resolution: "@babel/parser@npm:8.0.6"
dependencies:
- "@babel/types": "npm:^8.0.4"
+ "@babel/types": "npm:^8.0.6"
bin:
parser: ./bin/babel-parser.js
- checksum: 10c0/b98ebc350e6583ccec0c59ab653e6c5967935826b8e03210a96e638a98b0c552587d4ca9cc44502964bac42463e2bab93ca1a6ac519279a25e190bd78f3ce513
+ checksum: 10c0/af600f314542538a9dacf08e51cdce1eb947a6008e54539833987bdda98ef8632fd7178985411315431240e3d3d969788a216e534d6301eecbabed6d4a88a766
languageName: node
linkType: hard
@@ -2167,6 +2237,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/template@npm:^8.0.0":
+ version: 8.0.0
+ resolution: "@babel/template@npm:8.0.0"
+ dependencies:
+ "@babel/code-frame": "npm:^8.0.0"
+ "@babel/parser": "npm:^8.0.0"
+ "@babel/types": "npm:^8.0.0"
+ checksum: 10c0/8a5e5687299517cc6b0efff3a6342cf4c4256aeeffa14753cc8d08106aaf0ba1916707c0e1f385b511a7b42c6c87806fecaecd4fe06316207475e0d4d1148e2c
+ languageName: node
+ linkType: hard
+
"@babel/traverse@npm:latest":
version: 7.28.5
resolution: "@babel/traverse@npm:7.28.5"
@@ -2265,10 +2346,10 @@ __metadata:
languageName: node
linkType: hard
-"@blazediff/core@npm:1.9.1":
- version: 1.9.1
- resolution: "@blazediff/core@npm:1.9.1"
- checksum: 10c0/fd45cdd0544002341d74831a179ef693a81414abd348c1ff0c01086c0ea03f5e5ee284c4e16c2e6fb3670c265f90a3d85752b9360320efa9a835928e604dae77
+"@blazediff/core@npm:1.10.0":
+ version: 1.10.0
+ resolution: "@blazediff/core@npm:1.10.0"
+ checksum: 10c0/4b6095d39338fccd09964fff06f6c5a75628e6296cb2d598149376a876b5c390e083a3769e6ebdaeaa53550f29f1ed5744688df66e4f10b0be0bbc41e9393c8c
languageName: node
linkType: hard
@@ -3820,6 +3901,16 @@ __metadata:
languageName: node
linkType: hard
+"@jridgewell/gen-mapping@npm:0.4.0-beta.0":
+ version: 0.4.0-beta.0
+ resolution: "@jridgewell/gen-mapping@npm:0.4.0-beta.0"
+ dependencies:
+ "@jridgewell/sourcemap-codec": "npm:^1.6.0-beta.0"
+ "@jridgewell/trace-mapping": "npm:^0.3.24"
+ checksum: 10c0/3c8e3d13318cac007834b2ebc065e99574f5d12c1059e7fb58ad50e3b1cc6e422f610e29c2b90afeaad967a0e69503352b8b29b25954985e6bc8dfdb0031d53b
+ languageName: node
+ linkType: hard
+
"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.13, @jridgewell/gen-mapping@npm:^0.3.5":
version: 0.3.13
resolution: "@jridgewell/gen-mapping@npm:0.3.13"
@@ -3857,10 +3948,10 @@ __metadata:
languageName: node
linkType: hard
-"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5":
- version: 1.5.5
- resolution: "@jridgewell/sourcemap-codec@npm:1.5.5"
- checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0
+"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5, @jridgewell/sourcemap-codec@npm:^1.6.0, @jridgewell/sourcemap-codec@npm:^1.6.0-beta.0":
+ version: 1.6.0
+ resolution: "@jridgewell/sourcemap-codec@npm:1.6.0"
+ checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72
languageName: node
linkType: hard
@@ -10011,11 +10102,12 @@ __metadata:
"@types/micromatch": "npm:^4.0.0"
"@types/node": "npm:^22.19.1"
"@types/semver": "npm:^7.7.1"
- "@vitest/browser-playwright": "npm:^4.1.6"
- "@vitest/runner": "npm:^4.1.6"
+ "@vitest/browser-playwright": "npm:^5.0.0"
+ "@vitest/istanbul-lib-report": "npm:^1.0.1"
empathic: "npm:^2.0.0"
es-toolkit: "npm:^1.43.0"
istanbul-lib-report: "npm:^3.0.1"
+ istanbul-reports: "npm:^3.2.0"
micromatch: "npm:^4.0.8"
pathe: "npm:^1.1.2"
picocolors: "npm:^1.1.0"
@@ -10028,20 +10120,17 @@ __metadata:
tree-kill: "npm:^1.2.2"
ts-dedent: "npm:^2.2.0"
typescript: "npm:^6.0.3"
- vitest: "npm:^4.1.6"
+ vitest: "npm:^5.0.0"
peerDependencies:
- "@vitest/browser": ^3.0.0 || ^4.0.0 || ^5.0.0
+ "@vitest/browser": ^4.0.0 || ^5.0.0
"@vitest/browser-playwright": ^4.0.0 || ^5.0.0
- "@vitest/runner": ^3.0.0 || ^4.0.0 || ^5.0.0
storybook: "workspace:^"
- vitest: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vitest: ^4.0.0 || ^5.0.0
peerDependenciesMeta:
"@vitest/browser":
optional: true
"@vitest/browser-playwright":
optional: true
- "@vitest/runner":
- optional: true
vitest:
optional: true
languageName: unknown
@@ -10072,7 +10161,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@storybook/angular-vite@workspace:code/frameworks/angular-vite"
dependencies:
- "@analogjs/vite-plugin-angular": "npm:^2.5.2"
+ "@analogjs/vite-plugin-angular": "npm:^2.7.5"
"@angular-devkit/architect": "npm:^0.2102.12"
"@angular-devkit/build-angular": "npm:^21.2.12"
"@angular-devkit/core": "npm:^21.2.12"
@@ -10098,7 +10187,7 @@ __metadata:
vite: "npm:>=8.0.0"
zone.js: "npm:^0.16.2"
peerDependencies:
- "@analogjs/vite-plugin-angular": ">=2.7.3"
+ "@analogjs/vite-plugin-angular": ">=2.7.5"
"@angular-devkit/architect": ">=0.2100.0 < 0.2300.0"
"@angular-devkit/core": ">=21.0.0 < 23.0.0"
"@angular/build": ">=21.0.0 < 23.0.0"
@@ -10332,10 +10421,10 @@ __metadata:
"@types/react-dom": "npm:^18.0.11"
"@vitejs/plugin-react": "npm:^4.3.2"
"@vitejs/plugin-vue": "npm:^4.4.0"
- "@vitest/browser": "npm:^4.1.6"
- "@vitest/browser-playwright": "npm:^4.1.6"
- "@vitest/coverage-istanbul": "npm:^4.1.6"
- "@vitest/coverage-v8": "npm:^4.1.6"
+ "@vitest/browser": "npm:^5.0.0"
+ "@vitest/browser-playwright": "npm:^5.0.0"
+ "@vitest/coverage-istanbul": "npm:^5.0.0"
+ "@vitest/coverage-v8": "npm:^5.0.0"
chromatic: "npm:^18.0.1"
create-storybook: "workspace:*"
danger: "npm:^13.0.4"
@@ -10363,7 +10452,7 @@ __metadata:
uuid: "npm:^11.1.0"
vite: "npm:^7.0.4"
vite-plugin-inspect: "npm:^11.0.0"
- vitest: "npm:^4.1.6"
+ vitest: "npm:^5.0.0"
wait-on: "npm:^8.0.3"
dependenciesMeta:
ejs:
@@ -10883,7 +10972,7 @@ __metadata:
oxfmt: "npm:^0.41.0"
std-env: "npm:^4.0.0"
vite: "npm:^7.0.4"
- vitest: "npm:^4.1.6"
+ vitest: "npm:^5.0.0"
languageName: unknown
linkType: soft
@@ -10921,7 +11010,7 @@ __metadata:
"@types/wait-on": "npm:^5.3.4"
"@types/window-size": "npm:^1.1.4"
"@verdaccio/types": "npm:^10.8.0"
- "@vitest/coverage-v8": "npm:^4.1.6"
+ "@vitest/coverage-v8": "npm:^5.0.0"
chromatic: "npm:^18.0.1"
citty: "npm:^0.2.1"
codecov: "npm:^3.8.1"
@@ -10999,7 +11088,7 @@ __metadata:
uuid: "npm:^9.0.1"
verdaccio: "npm:^5.33.0"
verdaccio-auth-memory: "npm:^10.3.1"
- vitest: "npm:^4.1.6"
+ vitest: "npm:^5.0.0"
wait-on: "npm:^8.0.3"
window-size: "npm:^1.1.1"
yaml: "npm:^2.8.1"
@@ -12123,18 +12212,6 @@ __metadata:
languageName: node
linkType: hard
-"@ts-morph/common@npm:~0.22.0":
- version: 0.22.0
- resolution: "@ts-morph/common@npm:0.22.0"
- dependencies:
- fast-glob: "npm:^3.3.2"
- minimatch: "npm:^9.0.3"
- mkdirp: "npm:^3.0.1"
- path-browserify: "npm:^1.0.1"
- checksum: 10c0/1f1ff7fee54414e09803b1ba559ff51881b821c19bc97cb19d06fc46c58957dea4a58e6a83e9f2e30e08c8179d6d142e45be329d9242f410323795b5c1e04806
- languageName: node
- linkType: hard
-
"@ts-morph/common@npm:~0.29.0":
version: 0.29.0
resolution: "@ts-morph/common@npm:0.29.0"
@@ -12490,6 +12567,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/gensync@npm:^1.0.5":
+ version: 1.0.5
+ resolution: "@types/gensync@npm:1.0.5"
+ checksum: 10c0/7c37c8fc3b1a49efd8889f635a6204008f1c1cc86a09ac6a96e219cf3fc52cf1ff091c4dddc3160e93261d6e599fbba39f5fb698fbf936518d1c410289cdd640
+ languageName: node
+ linkType: hard
+
"@types/glob@npm:*":
version: 8.1.0
resolution: "@types/glob@npm:8.1.0"
@@ -14238,76 +14322,58 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/browser-playwright@npm:^4.1.6":
- version: 4.1.10
- resolution: "@vitest/browser-playwright@npm:4.1.10"
+"@vitest/browser-playwright@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "@vitest/browser-playwright@npm:5.0.1"
dependencies:
- "@vitest/browser": "npm:4.1.10"
- "@vitest/mocker": "npm:4.1.10"
- tinyrainbow: "npm:^3.1.0"
+ "@vitest/browser": "npm:5.0.1"
+ "@vitest/mocker": "npm:5.0.1"
+ tinyrainbow: "npm:^3.1.1"
peerDependencies:
playwright: "*"
- vitest: 4.1.10
+ vitest: 5.0.1
peerDependenciesMeta:
playwright:
optional: false
- checksum: 10c0/161530da4da9c061875c0e80c2c94e93658bf92514f2e5173c04299e17a6021feb209ef8fb5e14e880c8c8b2b9e7fc4f3ec7a346c8c4a77831864597331257fc
+ checksum: 10c0/152fd11e78a8fdea46a5e6537c5ce42ecd95b8744a7fe1cc59e4117b8e8220d1edf9bc55d28bc3c1cc51e7b758cbc0fd08b7d07794638c44dfbe5f546d84535f
languageName: node
linkType: hard
-"@vitest/browser@npm:4.1.10":
- version: 4.1.10
- resolution: "@vitest/browser@npm:4.1.10"
- dependencies:
- "@blazediff/core": "npm:1.9.1"
- "@vitest/mocker": "npm:4.1.10"
- "@vitest/utils": "npm:4.1.10"
- magic-string: "npm:^0.30.21"
- pngjs: "npm:^7.0.0"
- sirv: "npm:^3.0.2"
- tinyrainbow: "npm:^3.1.0"
- ws: "npm:^8.19.0"
- peerDependencies:
- vitest: 4.1.10
- checksum: 10c0/61c86b8c0fcc78bd01de559914525e768dc16d565ee8a40a41d51ef6d99595c3c5e976defe32d090b3dda5f77a980caa11a2072e9c4d279cfb8d55d31c565fc7
- languageName: node
- linkType: hard
-
-"@vitest/browser@npm:^4.1.6":
- version: 4.1.11
- resolution: "@vitest/browser@npm:4.1.11"
+"@vitest/browser@npm:5.0.1, @vitest/browser@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "@vitest/browser@npm:5.0.1"
dependencies:
- "@blazediff/core": "npm:1.9.1"
- "@vitest/mocker": "npm:4.1.11"
- "@vitest/utils": "npm:4.1.11"
- magic-string: "npm:^0.30.21"
+ "@blazediff/core": "npm:1.10.0"
+ "@vitest/mocker": "npm:5.0.1"
+ "@vitest/ui": "npm:5.0.1"
+ "@vitest/utils": "npm:5.0.1"
+ magic-string: "npm:^1.2.3"
pngjs: "npm:^7.0.0"
sirv: "npm:^3.0.2"
- tinyrainbow: "npm:^3.1.0"
- ws: "npm:^8.19.0"
+ tinyrainbow: "npm:^3.1.1"
+ ws: "npm:^8.21.3"
peerDependencies:
- vitest: 4.1.11
- checksum: 10c0/a16b95d043650e82e0706131454d259e660e11fca01d2ce9e336320a482626b2175051e12a93ea18efaf9a794f6cce221919fe59fe27fba04bd9f0e253e6957e
+ vitest: 5.0.1
+ checksum: 10c0/1fb73d5140aebbf9bbc93b4d09077051689afa7a8bbb45ae22f2136f3eddd1bcb6f44a63569b5fb09dded1846e79e9f3dd6e05a055d05af75de780ef6056a3e9
languageName: node
linkType: hard
-"@vitest/coverage-istanbul@npm:^4.1.6":
- version: 4.1.10
- resolution: "@vitest/coverage-istanbul@npm:4.1.10"
+"@vitest/coverage-istanbul@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "@vitest/coverage-istanbul@npm:5.0.1"
dependencies:
- "@babel/core": "npm:^7.29.0"
- "@istanbuljs/schema": "npm:^0.1.3"
"@jridgewell/gen-mapping": "npm:^0.3.13"
"@jridgewell/trace-mapping": "npm:0.3.31"
- istanbul-lib-coverage: "npm:^3.2.2"
- istanbul-lib-report: "npm:^3.0.1"
- istanbul-reports: "npm:^3.2.0"
- magicast: "npm:^0.5.2"
- obug: "npm:^2.1.1"
- tinyrainbow: "npm:^3.1.0"
+ "@vitest/istanbul-lib-coverage": "npm:^1.0.0"
+ "@vitest/istanbul-lib-instrument": "npm:^1.0.0"
+ "@vitest/istanbul-lib-report": "npm:^1.0.0"
+ "@vitest/istanbul-lib-source-maps": "npm:^1.0.0"
+ magicast: "npm:^0.5.4"
+ obug: "npm:^2.1.4"
+ tinyrainbow: "npm:^3.1.1"
peerDependencies:
- vitest: 4.1.10
- checksum: 10c0/a426b0aff94482587a100db7e8db4e238fabf9e5bc4d33a319bd9cd4261b8972a7b3d78f2bc7cbc3d11b0b58b5d7cc487f96ab35a96c1c766c54f9b6d5eb4f78
+ vitest: 5.0.1
+ checksum: 10c0/f7049c8a849f4f0ea48c9cbb3cf59cf7bf106ff52cf7a3e138dccf209d510f2616d2fe49adbc8c380cd30487d238059022fd7c8a0af0d192c57848c608d795f1
languageName: node
linkType: hard
@@ -14362,6 +14428,28 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/coverage-v8@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "@vitest/coverage-v8@npm:5.0.1"
+ dependencies:
+ "@bcoe/v8-coverage": "npm:^1.0.2"
+ "@vitest/istanbul-lib-coverage": "npm:^1.0.0"
+ "@vitest/istanbul-lib-report": "npm:^1.0.0"
+ ast-v8-to-istanbul: "npm:^1.0.5"
+ magicast: "npm:^0.5.4"
+ obug: "npm:^2.1.4"
+ std-env: "npm:^4.2.0"
+ tinyrainbow: "npm:^3.1.1"
+ peerDependencies:
+ "@vitest/browser": 5.0.1
+ vitest: 5.0.1
+ peerDependenciesMeta:
+ "@vitest/browser":
+ optional: true
+ checksum: 10c0/7f6dba018b7cd3303c625ff0c906bab971eef00214fac5d7edfd7ab3991bccbe7edf47f89273f49f7d2161e16fbdcc2b9938efd16f2da3bab8c31bf83777349e
+ languageName: node
+ linkType: hard
+
"@vitest/expect@npm:3.2.4":
version: 3.2.4
resolution: "@vitest/expect@npm:3.2.4"
@@ -14402,6 +14490,46 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/istanbul-lib-coverage@npm:1.0.1, @vitest/istanbul-lib-coverage@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "@vitest/istanbul-lib-coverage@npm:1.0.1"
+ checksum: 10c0/5259767db6c748a018c39554951e19818268fb6f72f74b74d2370471c7d83df990c3371247caea7d38f8e315df6dd461427dc242a1e7f5071e4b28863524f354
+ languageName: node
+ linkType: hard
+
+"@vitest/istanbul-lib-instrument@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "@vitest/istanbul-lib-instrument@npm:1.0.1"
+ dependencies:
+ "@babel/core": "npm:^8.0.1"
+ "@babel/parser": "npm:^8.0.4"
+ "@istanbuljs/schema": "npm:^0.1.3"
+ "@jridgewell/trace-mapping": "npm:^0.3.23"
+ "@vitest/istanbul-lib-coverage": "npm:1.0.1"
+ checksum: 10c0/627f2323f28ca8c4b7dd2c0842c1642f4a7b0058f7119eed9b7c909920a660cda73926f8aeda66b678947ac91ef0cfcaa0721e208879aaf4908170c8a5925319
+ languageName: node
+ linkType: hard
+
+"@vitest/istanbul-lib-report@npm:^1.0.0, @vitest/istanbul-lib-report@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "@vitest/istanbul-lib-report@npm:1.0.1"
+ dependencies:
+ "@vitest/istanbul-lib-coverage": "npm:1.0.1"
+ checksum: 10c0/1a76d4cd4c6284da0b897230d672769a5191235b3a0df41a3c861e3e12659d1790012705df6bad0f540e3f14b7b017f3611f99c9b9eb96972f8b55a3fb9270fe
+ languageName: node
+ linkType: hard
+
+"@vitest/istanbul-lib-source-maps@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "@vitest/istanbul-lib-source-maps@npm:1.0.1"
+ dependencies:
+ "@jridgewell/trace-mapping": "npm:^0.3.23"
+ "@vitest/istanbul-lib-coverage": "npm:1.0.1"
+ obug: "npm:^2.1.4"
+ checksum: 10c0/819e1f7b7d6b3cc2c34c91430fa966d3f0046842296ff8c2139d3e2237f97ca6fff9bfa285fccc073960e10ba6bea1bb09d115db2808acd0cff8dba4014b6e14
+ languageName: node
+ linkType: hard
+
"@vitest/mocker@npm:3.2.4":
version: 3.2.4
resolution: "@vitest/mocker@npm:3.2.4"
@@ -14440,13 +14568,14 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/mocker@npm:4.1.11":
- version: 4.1.11
- resolution: "@vitest/mocker@npm:4.1.11"
+"@vitest/mocker@npm:5.0.1":
+ version: 5.0.1
+ resolution: "@vitest/mocker@npm:5.0.1"
dependencies:
- "@vitest/spy": "npm:4.1.11"
+ "@jridgewell/trace-mapping": "npm:0.3.31"
+ "@vitest/spy": "npm:5.0.1"
estree-walker: "npm:^3.0.3"
- magic-string: "npm:^0.30.21"
+ magic-string: "npm:^1.2.3"
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -14455,7 +14584,7 @@ __metadata:
optional: true
vite:
optional: true
- checksum: 10c0/3111ea34bd5046f6c70bbd67cf8b89608ee8c41cb27ab2bd61d42f4b68810e9ea16a9a757a71bc254c105f73b407d00ebb6bab1ab0f7f5cdcc9d7d16602a3933
+ checksum: 10c0/21f0456c7452d7f65449fc0f43f1ccbf222d353eb11fcf511b78b5b8c4ae38ac9ca286b56936d91856c483575cd86688f1b6dc00f02ffd85ed1108b12cf219cd
languageName: node
linkType: hard
@@ -14477,12 +14606,12 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/pretty-format@npm:4.1.11":
- version: 4.1.11
- resolution: "@vitest/pretty-format@npm:4.1.11"
+"@vitest/pretty-format@npm:5.0.1":
+ version: 5.0.1
+ resolution: "@vitest/pretty-format@npm:5.0.1"
dependencies:
- tinyrainbow: "npm:^3.1.0"
- checksum: 10c0/ad32525c73807c0b72f38dc29bc51fd5a17879dc650f37995a9c5adbb8526e15f787691f76aa8768448ec7ed5bf5ba2b12329eac8fda1428b4d6b8c036390f71
+ tinyrainbow: "npm:^3.1.1"
+ checksum: 10c0/1fb025ae15b02b683b8ef8bd00babb585c6462bf9bc19985f39a8124f9d46489da943484a4c058b49123165489be5cb9bedac4ea41bf7c525380fff5e2182a0e
languageName: node
linkType: hard
@@ -14496,16 +14625,6 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/runner@npm:^4.1.6":
- version: 4.1.11
- resolution: "@vitest/runner@npm:4.1.11"
- dependencies:
- "@vitest/utils": "npm:4.1.11"
- pathe: "npm:^2.0.3"
- checksum: 10c0/3c782b055e9e688e1785f7c8937bd1669bad1b0e5758cb8b844f918f30a324b1d21e174d46c941ce80ebf37f2b89b55b6d619a675025914a1ece6377b95909e2
- languageName: node
- linkType: hard
-
"@vitest/snapshot@npm:4.1.10":
version: 4.1.10
resolution: "@vitest/snapshot@npm:4.1.10"
@@ -14534,10 +14653,26 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/spy@npm:4.1.11":
- version: 4.1.11
- resolution: "@vitest/spy@npm:4.1.11"
- checksum: 10c0/06c68247a8efd21006abe7532fee17f30ba83c8cda3e0b952ef89e278d58058f4b1de69b6bbaa2ed614c568bf4f5769fcc76be42802dedf2bcdd9c0aae601740
+"@vitest/spy@npm:5.0.1":
+ version: 5.0.1
+ resolution: "@vitest/spy@npm:5.0.1"
+ checksum: 10c0/88838f6dda5237f0a40c365598ce7112922611e9157b390ca90c11760ae8697c37214f621263bfbe12e2bd27234df577cbbecc93de003ced366d23be24383c37
+ languageName: node
+ linkType: hard
+
+"@vitest/ui@npm:5.0.1":
+ version: 5.0.1
+ resolution: "@vitest/ui@npm:5.0.1"
+ dependencies:
+ "@vitest/utils": "npm:5.0.1"
+ fflate: "npm:^0.8.3"
+ flatted: "npm:^3.4.4"
+ pathe: "npm:^2.0.3"
+ sirv: "npm:^3.0.2"
+ tinyrainbow: "npm:^3.1.1"
+ peerDependencies:
+ vitest: 5.0.1
+ checksum: 10c0/24db3f5c27080c54841cd32ba0c82f9df59066923f860a7a26ff9c33c17f3246e5f6d0dc86ea214b5d06d986d7f54a23e5ee45a7013bc5dccccee48a67e44490
languageName: node
linkType: hard
@@ -14563,14 +14698,14 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/utils@npm:4.1.11":
- version: 4.1.11
- resolution: "@vitest/utils@npm:4.1.11"
+"@vitest/utils@npm:5.0.1":
+ version: 5.0.1
+ resolution: "@vitest/utils@npm:5.0.1"
dependencies:
- "@vitest/pretty-format": "npm:4.1.11"
+ "@vitest/pretty-format": "npm:5.0.1"
convert-source-map: "npm:^2.0.0"
- tinyrainbow: "npm:^3.1.0"
- checksum: 10c0/a2c1ddc64333458c3e031465c1ee0440a7660fd1b298c54ccbf865ef1e5cf3ebdd6c5c75a5ea235d8a672498b394e7121f992b096f021f45014ab25e9abd1b52
+ tinyrainbow: "npm:^3.1.1"
+ checksum: 10c0/c16c5d0cf3795e5c3bc46212bc03a4192fb2e515cf51e1465188578d591a1feeacb911c0d554ebbb6e1dcf115e96a528b6175dedf0b2a4f8b4ce4a5ce61ab5bd
languageName: node
linkType: hard
@@ -15813,14 +15948,14 @@ __metadata:
languageName: node
linkType: hard
-"ast-v8-to-istanbul@npm:^1.0.0":
- version: 1.0.0
- resolution: "ast-v8-to-istanbul@npm:1.0.0"
+"ast-v8-to-istanbul@npm:^1.0.0, ast-v8-to-istanbul@npm:^1.0.5":
+ version: 1.0.7
+ resolution: "ast-v8-to-istanbul@npm:1.0.7"
dependencies:
"@jridgewell/trace-mapping": "npm:^0.3.31"
estree-walker: "npm:^3.0.3"
js-tokens: "npm:^10.0.0"
- checksum: 10c0/35e57b754ba63287358094d4f7ae8de2de27286fb4e76a1fbf28b2e67e3b670b59c3f511882473d0fd2cdbaa260062e3cd4f216b724c70032e2b09e5cebbd618
+ checksum: 10c0/c6bbfbf8b8adc8d793f8d10fbf9bcb25b0c65f2faa73ec1e41d2937a9c18327167b9395134943a4814dd3d095d2453b2f0f71a5d33aecd808ac4725c23e69918
languageName: node
linkType: hard
@@ -17574,13 +17709,6 @@ __metadata:
languageName: node
linkType: hard
-"code-block-writer@npm:^12.0.0":
- version: 12.0.0
- resolution: "code-block-writer@npm:12.0.0"
- checksum: 10c0/ced73cdc466bff968bba9e8e32340d88420d25a229b9269f7425a10a7c2c9a12ca702dcb601b2462b96472d354f021cf66e552179fcbe30c8f7ecd0173c5fa07
- languageName: node
- linkType: hard
-
"code-block-writer@npm:^13.0.3":
version: 13.0.3
resolution: "code-block-writer@npm:13.0.3"
@@ -19484,13 +19612,20 @@ __metadata:
languageName: node
linkType: hard
-"empathic@npm:2.0.0, empathic@npm:^2.0.0":
+"empathic@npm:2.0.0":
version: 2.0.0
resolution: "empathic@npm:2.0.0"
checksum: 10c0/7d3b14b04a93b35c47bcc950467ec914fd241cd9acc0269b0ea160f13026ec110f520c90fae64720fde72cc1757b57f3f292fb606617b7fccac1f4d008a76506
languageName: node
linkType: hard
+"empathic@npm:^2.0.0, empathic@npm:^2.0.1":
+ version: 2.1.0
+ resolution: "empathic@npm:2.1.0"
+ checksum: 10c0/cf13e2919d680a22140d7cd1637a01f6cf9a727f21d3a54b23489ec01c80eab548b9d832d2e167d00e124a54377660c1d6fd2b5947d905366e4d22fde7b9e082
+ languageName: node
+ linkType: hard
+
"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0":
version: 2.0.0
resolution: "encodeurl@npm:2.0.0"
@@ -19856,10 +19991,10 @@ __metadata:
languageName: node
linkType: hard
-"es-module-lexer@npm:^2.0.0":
- version: 2.0.0
- resolution: "es-module-lexer@npm:2.0.0"
- checksum: 10c0/ae78dbbd43035a4b972c46cfb6877e374ea290adfc62bc2f5a083fea242c0b2baaab25c5886af86be55f092f4a326741cb94334cd3c478c383fdc8a9ec5ff817
+"es-module-lexer@npm:^2.0.0, es-module-lexer@npm:^2.3.2":
+ version: 2.3.2
+ resolution: "es-module-lexer@npm:2.3.2"
+ checksum: 10c0/5e7389424c43478439f12f9a6aca1750f6f99afa384fc3de329f4a45f152ab156671055008adaafc74960877abf8cc338aeebf6bed8c21297b146fd6eb7a22f8
languageName: node
linkType: hard
@@ -20795,7 +20930,7 @@ __metadata:
languageName: node
linkType: hard
-"expect-type@npm:^1.3.0":
+"expect-type@npm:^1.3.0, expect-type@npm:^1.4.0":
version: 1.4.0
resolution: "expect-type@npm:1.4.0"
checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7
@@ -21237,6 +21372,13 @@ __metadata:
languageName: node
linkType: hard
+"fflate@npm:^0.8.3":
+ version: 0.8.3
+ resolution: "fflate@npm:0.8.3"
+ checksum: 10c0/eab181ca37f5348ae76d4b6f840e0026e30220e33153289ac942222d8b9638237d486507dbcc09878d724095bd354993a2ee48bbee99c8f2c6440d4448719aa7
+ languageName: node
+ linkType: hard
+
"figures@npm:3.2.0":
version: 3.2.0
resolution: "figures@npm:3.2.0"
@@ -21508,10 +21650,10 @@ __metadata:
languageName: node
linkType: hard
-"flatted@npm:^3.2.9":
- version: 3.2.9
- resolution: "flatted@npm:3.2.9"
- checksum: 10c0/5c91c5a0a21bbc0b07b272231e5b4efe6b822bcb4ad317caf6bb06984be4042a9e9045026307da0fdb4583f1f545e317a67ef1231a59e71f7fced3cc429cfc53
+"flatted@npm:^3.2.9, flatted@npm:^3.4.4":
+ version: 3.4.4
+ resolution: "flatted@npm:3.4.4"
+ checksum: 10c0/a3a52a88ea5a4c333e5a1f097dcd87a037fa31c236a77cf46222c2aa4036ef895ab9bc76138a66d9dc22bdb3652128f9598cd26e7439561bf19f67276e2bc37e
languageName: node
linkType: hard
@@ -21536,6 +21678,13 @@ __metadata:
languageName: node
linkType: hard
+"flru@npm:1.0.2":
+ version: 1.0.2
+ resolution: "flru@npm:1.0.2"
+ checksum: 10c0/73b01b1df3f3f9d56a86c2102ac199ac72918ee7a00f8516be8dd4495da90ede9375873ad8902ca93a23c91bcc224aacf2dcf89bf7ce62efb9faf048b564b412
+ languageName: node
+ linkType: hard
+
"flush-promises@npm:^1.0.2":
version: 1.0.2
resolution: "flush-promises@npm:1.0.2"
@@ -23401,6 +23550,13 @@ __metadata:
languageName: node
linkType: hard
+"import-meta-resolve@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "import-meta-resolve@npm:4.2.0"
+ checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c
+ languageName: node
+ linkType: hard
+
"imurmurhash@npm:^0.1.4":
version: 0.1.4
resolution: "imurmurhash@npm:0.1.4"
@@ -25815,12 +25971,12 @@ __metadata:
languageName: node
linkType: hard
-"magic-string@npm:^1.0.0, magic-string@npm:^1.1.0":
- version: 1.2.3
- resolution: "magic-string@npm:1.2.3"
+"magic-string@npm:^1.0.0, magic-string@npm:^1.1.0, magic-string@npm:^1.2.3":
+ version: 1.4.2
+ resolution: "magic-string@npm:1.4.2"
dependencies:
- "@jridgewell/sourcemap-codec": "npm:^1.5.5"
- checksum: 10c0/541ecb30ec96e4d9b76ca65aed00124592dca26739aeee86e01d7fbce227b3fce43c9d2f0b9495dc9077c30794c967b1e091445614e2e45722ea4c128be60a93
+ "@jridgewell/sourcemap-codec": "npm:^1.6.0"
+ checksum: 10c0/d3e222c202f99ec4861e7316f473f9b1cfe335ebdf38d4f67335f842f3fe248f01ee00fd3e1a8206bcbe9855e0c41515d7c210300d8fcf6eefbb982d79e423c6
languageName: node
linkType: hard
@@ -25835,14 +25991,14 @@ __metadata:
languageName: node
linkType: hard
-"magicast@npm:^0.5.2":
- version: 0.5.2
- resolution: "magicast@npm:0.5.2"
+"magicast@npm:^0.5.2, magicast@npm:^0.5.4":
+ version: 0.5.5
+ resolution: "magicast@npm:0.5.5"
dependencies:
- "@babel/parser": "npm:^7.29.0"
- "@babel/types": "npm:^7.29.0"
+ "@babel/parser": "npm:^7.29.7"
+ "@babel/types": "npm:^7.29.7"
source-map-js: "npm:^1.2.1"
- checksum: 10c0/924af677643c5a0a7d6cdb3247c0eb96fa7611b2ba6a5e720d35d81c503d3d9f5948eb5227f80f90f82ea3e7d38cffd10bb988f3fc09020db428e14f26e960d7
+ checksum: 10c0/d27e94cd1e85f1736f97ab0c6e542170a818c7e573261722d056b3ec4a520e16d836ce4c6b0cd3dd215b1171d83592d48a3736f3da5997a55629a0de05f66e7d
languageName: node
linkType: hard
@@ -26771,7 +26927,7 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^9.0.3, minimatch@npm:^9.0.4":
+"minimatch@npm:^9.0.4":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
@@ -26931,15 +27087,6 @@ __metadata:
languageName: node
linkType: hard
-"mkdirp@npm:^3.0.1":
- version: 3.0.1
- resolution: "mkdirp@npm:3.0.1"
- bin:
- mkdirp: dist/cjs/src/bin.js
- checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d
- languageName: node
- linkType: hard
-
"mktemp@npm:~0.4.0":
version: 0.4.0
resolution: "mktemp@npm:0.4.0"
@@ -27974,10 +28121,10 @@ __metadata:
languageName: node
linkType: hard
-"obug@npm:^2.1.1, obug@npm:^2.1.3":
- version: 2.1.3
- resolution: "obug@npm:2.1.3"
- checksum: 10c0/cb8187fed0a5fc8445507c950e89f3c1bd43895658c398b5803f6b7804dfa0c562975ecce1e67f3d9247d521452a5bfade9e0e951cc0326b7444272f7c24d25f
+"obug@npm:^2.1.1, obug@npm:^2.1.3, obug@npm:^2.1.4":
+ version: 2.2.1
+ resolution: "obug@npm:2.2.1"
+ checksum: 10c0/8a7c001847d880fa6ba83c7bbee6a80fd271ee628439cd508c8647d53b217abb1759e2d5e4aaeb09a35d0a274bdb225a0a5a78eded3213d63e656153cf411b63
languageName: node
linkType: hard
@@ -33578,10 +33725,10 @@ __metadata:
languageName: node
linkType: hard
-"std-env@npm:^4.0.0, std-env@npm:^4.0.0-rc.1":
- version: 4.0.0
- resolution: "std-env@npm:4.0.0"
- checksum: 10c0/63b1716eae27947adde49e21b7225a0f75fb2c3d410273ae9de8333c07c7d5fc7a0628ae4c8af6b4b49b4274ed46c2bf118ed69b64f1261c9d8213d76ed1c16c
+"std-env@npm:^4.0.0, std-env@npm:^4.0.0-rc.1, std-env@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "std-env@npm:4.2.0"
+ checksum: 10c0/40ac525ce7b7c556abc332a7376f14356eeb1a7f17f6ff9a003eb9f52326ff1f3745d3e1b43452675b1ec6fcc319f1b1d6f3b0d386cf3f91058479ad883cff69
languageName: node
linkType: hard
@@ -34721,6 +34868,13 @@ __metadata:
languageName: node
linkType: hard
+"tinybench@npm:6.1.4":
+ version: 6.1.4
+ resolution: "tinybench@npm:6.1.4"
+ checksum: 10c0/7a815318a02270a98247298ddd289b75c841725606c977f0b74749bc5ec811a44998267ccb60bd3deb23b551dbfaf4d979eb08e9b04fbc87ff571f9b39cc61c3
+ languageName: node
+ linkType: hard
+
"tinybench@npm:^2.9.0":
version: 2.9.0
resolution: "tinybench@npm:2.9.0"
@@ -34742,10 +34896,10 @@ __metadata:
languageName: node
linkType: hard
-"tinyexec@npm:^1.0.2, tinyexec@npm:^1.0.4":
- version: 1.0.4
- resolution: "tinyexec@npm:1.0.4"
- checksum: 10c0/d4a5bbcf6bdb23527a4b74c4aa566f41432167112fe76f420ec7e3a90a3ecfd3a7d944383e2719fc3987b69400f7b928daf08700d145fb527c2e80ec01e198bd
+"tinyexec@npm:1.3.0, tinyexec@npm:^1.0.2, tinyexec@npm:^1.0.4":
+ version: 1.3.0
+ resolution: "tinyexec@npm:1.3.0"
+ checksum: 10c0/e9b89f97489d2aab2cef408da279e6b32547e738d1275032ccb8fd0028a006d93eb70fc51c6cffd9fc2f5aca6c2a273d8b6f73b52d46ee5116da6b94969ef958
languageName: node
linkType: hard
@@ -34783,10 +34937,10 @@ __metadata:
languageName: node
linkType: hard
-"tinyrainbow@npm:^3.1.0":
- version: 3.1.0
- resolution: "tinyrainbow@npm:3.1.0"
- checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511
+"tinyrainbow@npm:^3.1.0, tinyrainbow@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "tinyrainbow@npm:3.1.1"
+ checksum: 10c0/f9d2743832c6191f753408f36224fe817620b8abcef572b2e570204c673a901d753ff84ca8e7b88f9c79e934295b3ffc6fcbc56a06f126e24e1ec6186dcad40d
languageName: node
linkType: hard
@@ -35094,16 +35248,6 @@ __metadata:
languageName: node
linkType: hard
-"ts-morph@npm:^21.0.0":
- version: 21.0.1
- resolution: "ts-morph@npm:21.0.1"
- dependencies:
- "@ts-morph/common": "npm:~0.22.0"
- code-block-writer: "npm:^12.0.0"
- checksum: 10c0/ed1d4ccdeba2300cfa236f2aaf64bb462aa386141e659a08d8a2eb96d3b4b274abc9d97b8dd06a0c13af79187b197f38f6a8baca709f99d11094a82c8abccf27
- languageName: node
- linkType: hard
-
"ts-morph@npm:^28.0.0":
version: 28.0.0
resolution: "ts-morph@npm:28.0.0"
@@ -36267,6 +36411,13 @@ __metadata:
languageName: node
linkType: hard
+"verkit@npm:^0.3.2":
+ version: 0.3.2
+ resolution: "verkit@npm:0.3.2"
+ checksum: 10c0/2533d9cc4ee6514d7f0d07b035c56a1f3eedd3ec5c3a5b3df38af1f2e9412799476c250f51d034562ddeb36dea72d4530f840e7b80759bd9a527f31cc9aa54e7
+ languageName: node
+ linkType: hard
+
"verror@npm:1.10.0":
version: 1.10.0
resolution: "verror@npm:1.10.0"
@@ -36660,6 +36811,67 @@ __metadata:
languageName: node
linkType: hard
+"vitest@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "vitest@npm:5.0.1"
+ dependencies:
+ "@types/chai": "npm:^5.2.2"
+ "@vitest/mocker": "npm:5.0.1"
+ chai: "npm:^6.2.2"
+ es-module-lexer: "npm:^2.3.2"
+ expect-type: "npm:^1.4.0"
+ magic-string: "npm:^1.2.3"
+ obug: "npm:^2.1.4"
+ picomatch: "npm:^4.0.7"
+ std-env: "npm:^4.2.0"
+ tinybench: "npm:6.1.4"
+ tinyexec: "npm:1.3.0"
+ tinyglobby: "npm:^0.2.17"
+ why-is-node-running: "npm:^2.3.0"
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@opentelemetry/api": ^1.9.0
+ "@types/node": ^22.0.0 || >=24.0.0
+ "@vitest/browser-playwright": 5.0.1
+ "@vitest/browser-preview": 5.0.1
+ "@vitest/browser-webdriverio": ^5.0.0-beta.5 || >=5.0.0
+ "@vitest/coverage-istanbul": 5.0.1
+ "@vitest/coverage-v8": 5.0.1
+ "@vitest/ui": 5.0.1
+ happy-dom: "*"
+ jsdom: "*"
+ vite: ^6.4.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@opentelemetry/api":
+ optional: true
+ "@types/node":
+ optional: true
+ "@vitest/browser-playwright":
+ optional: true
+ "@vitest/browser-preview":
+ optional: true
+ "@vitest/browser-webdriverio":
+ optional: true
+ "@vitest/coverage-istanbul":
+ optional: true
+ "@vitest/coverage-v8":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ vite:
+ optional: false
+ bin:
+ vitest: ./vitest.mjs
+ checksum: 10c0/1b49fe8eb7274ffd43ca5a29cccbcc942cf991488020a74114f3a725a1168c549d47820715b8ed3f521a0cd9ccc0013ab9511cc6e0f8e0a5bdfb751e3ddc4d2d
+ languageName: node
+ linkType: hard
+
"vlq@npm:^0.2.1":
version: 0.2.3
resolution: "vlq@npm:0.2.3"
@@ -37434,9 +37646,9 @@ __metadata:
languageName: node
linkType: hard
-"ws@npm:^8.18.0, ws@npm:^8.19.0, ws@npm:^8.21.0, ws@npm:^8.21.1":
- version: 8.21.1
- resolution: "ws@npm:8.21.1"
+"ws@npm:^8.18.0, ws@npm:^8.21.0, ws@npm:^8.21.1, ws@npm:^8.21.3":
+ version: 8.21.3
+ resolution: "ws@npm:8.21.3"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
@@ -37445,7 +37657,7 @@ __metadata:
optional: true
utf-8-validate:
optional: true
- checksum: 10c0/c4c6f1d95f6d465262de2037c57c715725d67e078dd49420ede4e19115668aba159cb64d85c5e89c06eb2826be599e62e5095860ad6cc54ff42e8bd7684e1db8
+ checksum: 10c0/7b28dc2863ea0e2cece68d142a3eee90361021b73f750431e6d8076bb7dede5fdfdb75b3d29534b62f411261147b76b1bc80fb5cc63ab0aabd467280e85b22e0
languageName: node
linkType: hard