diff --git a/scripts/ci/main.ts b/scripts/ci/main.ts
index f4c5ac3d1f21..744d4d308b7a 100644
--- a/scripts/ci/main.ts
+++ b/scripts/ci/main.ts
@@ -52,7 +52,7 @@ function generateConfig(workflow: Workflow) {
jobs.push(fmt);
} else {
const sandboxes = getSandboxes(workflow);
- const testStorybooks = getTestStorybooks(workflow);
+ const testStorybooks = getTestStorybooks();
const initEmpty = getInitEmpty(workflow);
if (isWorkflowOrAbove(workflow, 'daily')) {
diff --git a/scripts/ci/test-storybooks.ts b/scripts/ci/test-storybooks.ts
index d8963ad886c5..a52d2c0f33e5 100644
--- a/scripts/ci/test-storybooks.ts
+++ b/scripts/ci/test-storybooks.ts
@@ -3,13 +3,7 @@ import { join } from 'path/posix';
import { build_linux } from './common-jobs.ts';
import { artifact, workflow } from './utils/helpers.ts';
-import {
- type JobOrNoOpJob,
- type Workflow,
- defineJob,
- defineNoOpJob,
- isWorkflowOrAbove,
-} from './utils/types.ts';
+import { type JobOrNoOpJob, defineJob, defineNoOpJob } from './utils/types.ts';
export function definePortableStoryTest(directory: string) {
const working_directory = `test-storybooks/portable-stories-kitchen-sink/${directory}`;
@@ -76,39 +70,6 @@ export function definePortableStoryTest(directory: string) {
);
}
-export function definePortableStoryTestVitest3() {
- return defineJob(
- 'test-storybooks-portable-vitest3',
- () => ({
- executor: {
- name: 'sb_playwright',
- class: 'medium',
- },
- steps: [
- ...workflow.restoreLinux(),
- {
- run: {
- name: 'Install dependencies',
- working_directory: 'test-storybooks/portable-stories-kitchen-sink/react-vitest-3',
- command: 'yarn install --no-immutable',
- environment: {
- YARN_ENABLE_IMMUTABLE_INSTALLS: false,
- },
- },
- },
- {
- run: {
- name: 'Run Playwright E2E tests',
- working_directory: 'test-storybooks/portable-stories-kitchen-sink/react-vitest-3',
- command: 'yarn playwright-e2e',
- },
- },
- ],
- }),
- [testStorybooksNoOpJob]
- );
-}
-
export function defineMcpTestStorybook() {
return defineJob(
'test-storybooks-mcp',
@@ -160,16 +121,12 @@ export function defineMcpTestStorybook() {
export const testStorybooksNoOpJob = defineNoOpJob('test-storybooks', [build_linux]);
-export function getTestStorybooks(workflow: Workflow) {
+export function getTestStorybooks() {
const testStorybooks: JobOrNoOpJob[] = ['react', 'vue3', 'svelte', 'nextjs'].map(
definePortableStoryTest
);
testStorybooks.push(defineMcpTestStorybook());
- if (isWorkflowOrAbove(workflow, 'merged')) {
- testStorybooks.push(definePortableStoryTestVitest3());
- }
-
return testStorybooks;
}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs
deleted file mode 100644
index 29cb6d5a0877..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.eslintrc.cjs
+++ /dev/null
@@ -1,14 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.gitignore b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.gitignore
deleted file mode 100644
index b88c8135abcd..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.gitignore
+++ /dev/null
@@ -1,28 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-/test-results/
-/playwright-report/
-/blob-report/
-/playwright/.cache/
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/aliased.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/aliased.ts
deleted file mode 100644
index 030574bfd2a6..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/aliased.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// This file is used to test that viteFinal is correctly loaded in Vitest
-// main.ts defines this file as a resolve alias, which is used in the ViteFinalTest story
-
-export const aliasedFunction = () => true;
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts
deleted file mode 100644
index 794f14db5674..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/get-decorator-string.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const getDecoratorString = () => {
- return "Global Decorator";
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts
deleted file mode 100644
index fa0ee95488bf..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/main.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { join } from "node:path";
-
-import type { StorybookConfig } from "@storybook/react-vite";
-
-const config: StorybookConfig = {
- stories: ["../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
- addons: ["@storybook/addon-vitest", "@storybook/addon-a11y"],
- framework: {
- name: "@storybook/react-vite",
- options: {},
- },
- core: {
- disableWhatsNewNotifications: true,
- },
- previewHead: (head = "") => `${head}
- `,
- staticDirs: [{ from: "./test-static-dirs", to: "test-static-dirs" }],
- viteFinal: (config) => {
- return {
- ...config,
- optimizeDeps: {
- ...config.optimizeDeps,
- include: [...(config.optimizeDeps?.include || [])],
- },
- resolve: {
- ...config.resolve,
- alias: {
- ...config.resolve?.alias,
- "test-alias": join(import.meta.dirname, "aliased.ts"),
- },
- },
- };
- },
- refs: {
- "storybook@8.0.0": {
- title: "Storybook 8.0.0",
- url: "https://635781f3500dd2c49e189caf-gckybvsekn.chromatic.com/",
- },
- "storybook@7.6.18": {
- title: "Storybook 7.6.18",
- url: "https://635781f3500dd2c49e189caf-oljwjdrftz.chromatic.com/",
- },
- },
-};
-
-export default config;
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview-head.html b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview-head.html
deleted file mode 100644
index 61252d019910..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview-head.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview.tsx
deleted file mode 100644
index ee02f0c8d810..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/preview.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { Preview } from '@storybook/react-vite';
-import { getDecoratorString } from './get-decorator-string';
-
-console.log('preview file is called!');
-
-const preview: Preview = {
- decorators: [
- (StoryFn) => (
-
-
{getDecoratorString()}
-
-
-
- ),
- ],
- initialGlobals: {
- locale: 'en',
- },
- globalTypes: {
- locale: {
- description: 'Locale for components',
- toolbar: {
- title: 'Locale',
- icon: 'circlehollow',
- items: ['es', 'en'],
- },
- },
- },
-};
-
-export default preview;
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts
deleted file mode 100644
index 592e8f52b66b..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/setup-file-dependency.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const getString = () => {
- return "initial string";
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/test-static-dirs/static.js b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/test-static-dirs/static.js
deleted file mode 100644
index 56ff9f9c7ad4..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/test-static-dirs/static.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// This file is used to test that staticDirs are correctly loaded in Vitest
-
-export const staticFunction = () => true;
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/vitest.setup.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/vitest.setup.ts
deleted file mode 100644
index 51fc3ac60150..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/vitest.setup.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { getString } from './setup-file-dependency';
-
-console.log(getString());
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.yarnrc b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.yarnrc
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.yarnrc.yml b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.yarnrc.yml
deleted file mode 100644
index e02860e70d88..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.yarnrc.yml
+++ /dev/null
@@ -1 +0,0 @@
-yarnPath: ../../../.yarn/releases/yarn-4.18.0.cjs
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts
deleted file mode 100644
index 6998b3c7793c..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts
+++ /dev/null
@@ -1,822 +0,0 @@
-import { promises as fs } from "node:fs";
-import path from "node:path";
-
-import { expect, test } from "@playwright/test";
-
-import { SbPage } from "../../../../code/e2e-sandbox/util";
-
-const STORYBOOK_URL = "http://localhost:6006";
-const TEST_STORY_PATH = path.resolve(
- __dirname,
- "..",
- "stories",
- "AddonTest.stories.tsx"
-);
-const UNHANDLED_ERRORS_STORY_PATH = path.resolve(
- __dirname,
- "..",
- "stories",
- "UnhandledErrors.stories.tsx"
-);
-const ADDON_TEST_DEPENDENCY_PATH = path.resolve(
- __dirname,
- "..",
- "stories",
- "get-button-string.ts"
-);
-const PREVIEW_DEPENDENCY_PATH = path.resolve(
- __dirname,
- "..",
- ".storybook",
- "get-decorator-string.ts"
-);
-const SETUP_FILE_DEPENDENCY_PATH = path.resolve(
- __dirname,
- "..",
- ".storybook",
- "setup-file-dependency.ts"
-);
-
-const setForceFailureFlag = (content: string, value: boolean) =>
- content.replace(/forceFailure:\s*(true|false)/, `forceFailure: ${value}`);
-
-const modifiedFiles = new Map();
-
-const modifyFile = async (
- filePath: string,
- modify: (content: string) => string
-) => {
- const content = (await fs.readFile(filePath)).toString();
- const modifiedContent = modify(content);
- await fs.writeFile(filePath, modifiedContent);
- if (!modifiedFiles.has(filePath)) {
- modifiedFiles.set(filePath, content);
- }
-
- // the file change causes a HMR event, which causes a browser reload,and that can take a few seconds
- await new Promise((resolve) => setTimeout(resolve, 2000));
-};
-
-const restoreAllFiles = async () => {
- for (const [filePath, originalContent] of modifiedFiles.entries()) {
- await fs.writeFile(filePath, originalContent);
- }
- modifiedFiles.clear();
- // the file change causes a HMR event, which causes a browser reload,and that can take a few seconds
- await new Promise((resolve) => setTimeout(resolve, 2000));
-};
-
-test.describe("component testing", () => {
- test.describe.configure({ mode: "serial" });
- test.beforeEach(async ({ page }) => {
- const sbPage = new SbPage(page, expect);
-
- await page.goto(STORYBOOK_URL);
- await page.evaluate(() => window.sessionStorage.clear());
- await sbPage.waitUntilLoaded();
-
- const expandTestingModule = page.getByLabel("Expand testing module");
- if (await expandTestingModule.isVisible()) {
- await expandTestingModule.click();
- }
- });
-
- test.afterEach(async ({ page }) => {
- await page.click("body");
- try {
- /** Sometimes the vitest instance fails, but the error-content is only shown in a modal.
- * We click the link so the modal opens and we can close it.
- * Having it open shortly is enough to have it be in the playwright trace, for debugging purposes.
- */
- const descriptionButton = page.locator("#testing-module-description button");
- if (
- await descriptionButton.isVisible({ timeout: 4000 }).catch(() => false)
- ) {
- await descriptionButton.click({ timeout: 4000, force: true });
-
- await page
- .getByLabel("Close modal")
- .click({ timeout: 4000, force: true });
- }
- } catch {
- // Ignore any errors when trying to open the modal
- }
-
- await restoreAllFiles();
-
- const expandTestingModule = page.getByLabel("Expand testing module");
- if (await expandTestingModule.isVisible()) {
- await expandTestingModule.click();
- }
-
- // Make sure any popover is closed
- await page.click("body");
-
- // Ensure that all test results are removed and features are disabled, as previous tests might have enabled them
- const clearStatusesButton = page.getByLabel("Clear all statuses");
- if (await clearStatusesButton.isVisible()) {
- await clearStatusesButton.click();
- }
-
- const watchModeToggle = page.getByRole("switch", { name: "Watch mode" });
- if (
- (await watchModeToggle.isVisible()) &&
- (await watchModeToggle.getAttribute("aria-checked")) === "true"
- ) {
- await watchModeToggle.click();
- }
-
- const configs = [
- page.getByRole("checkbox", { name: "Coverage" }),
- page.getByRole("checkbox", { name: "Accessibility" }),
- ];
- for (const config of configs) {
- if (await config.isChecked()) {
- await config.click();
- }
- }
- });
-
- test("should show discrepancy between test results", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- const sbPage = new SbPage(page, expect);
-
- await sbPage.navigateToStory("addons/group/test", "Mismatch Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await sbPage.viewAddonPanel("Interactions");
-
- // For whatever reason, when visiting a story sometimes the story element is collapsed and that causes flake
- const testStoryElement = await page.getByRole("button", {
- name: "Test",
- exact: true,
- });
- if ((await testStoryElement.getAttribute("aria-expanded")) !== "true") {
- await testStoryElement.click();
- }
-
- const testingModuleDescription = await page.locator(
- "#testing-module-description"
- );
-
- const runTestsButton = await page.getByLabel("Start test run");
- await runTestsButton.click();
-
- // Wait for test results to appear
- await expect(testingModuleDescription).toHaveText(/Ran \d+ tests/, {
- timeout: 60000,
- });
-
- const errorFilter = page.getByLabel(
- /Filter main navigation to show \d+ tests with errors/
- );
- await expect(errorFilter).toBeVisible();
-
- // Assert discrepancy: CLI pass + Browser fail
- const failingStoryElement = page.locator(
- '[data-item-id="addons-group-test--mismatch-failure"] [data-testid="tree-status-button"]'
- );
- await expect(failingStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Success"
- );
- await expect(sbPage.panelContent()).toContainText(
- /This interaction test passed in the CLI, but the tests failed in this browser/
- );
-
- // Assert discrepancy: CLI fail + Browser pass
- await sbPage.navigateToStory("addons/group/test", "Mismatch Success");
- const successfulStoryElement = page.locator(
- '[data-item-id="addons-group-test--mismatch-success"] [data-testid="tree-status-button"]'
- );
- await expect(successfulStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Error"
- );
- await expect(sbPage.panelContent()).toContainText(
- /This interaction test passed in this browser, but the tests failed in the CLI/
- );
- });
-
- test("should execute tests via testing module UI", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, true)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await expect(page.locator("#testing-module-title")).toHaveText(
- "Run component tests"
- );
-
- const runTestsButton = await page.getByLabel("Start test run");
- const watchModeButton = await page.getByRole("switch", {
- name: "Watch mode",
- });
- await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true");
- await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true");
-
- await runTestsButton.click();
-
- // Wait for test results to appear
- await expect(page.locator("#testing-module-description")).toHaveText(
- /Ran \d+ tests/,
- { timeout: 30000 }
- );
-
- await expect(runTestsButton).not.toHaveAttribute("aria-disabled", "true");
- await expect(watchModeButton).not.toHaveAttribute("aria-disabled", "true");
-
- const errorFilter = page.getByLabel(
- /Filter main navigation to show \d+ tests with errors/
- );
- await expect(errorFilter).toBeVisible();
-
- // Assert for expected success
- const successfulStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]'
- );
- await expect(successfulStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Success"
- );
-
- // Assert for expected failure
- const failingStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]'
- );
- await expect(failingStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Error"
- );
-
- // Assert that filter works as intended
- await errorFilter.click();
-
- const sidebarItems = page.locator(
- '.sidebar-item[data-ref-id="storybook_internal"][data-nodetype="component"]'
- );
- await expect(sidebarItems).toHaveCount(2);
- });
-
- test("should run tests in watch mode when a story file is changed", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await page.getByRole("switch", { name: "Watch mode" }).click();
-
- // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to
- await page.waitForTimeout(8000);
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, true)
- );
-
- // Wait for test results to appear
- const errorFilter = page.getByLabel(
- /Filter main navigation to show \d+ tests with errors/
- );
- await expect(errorFilter).toBeVisible({ timeout: 30000 });
-
- // Assert for expected success
- const successfulStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-success"] [data-testid="tree-status-button"]'
- );
- await expect(successfulStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Success"
- );
-
- // Assert for expected failure
- const failingStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-failure"] [data-testid="tree-status-button"]'
- );
- await expect(failingStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Error"
- );
-
- // Assert that filter works as intended
- await errorFilter.click();
-
- const sidebarItems = page.locator(
- '.sidebar-item[data-ref-id="storybook_internal"][data-nodetype="component"]'
- );
- await expect(sidebarItems).toHaveCount(1);
- });
-
- test("should run tests in watch mode when a story file's dependency is changed", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await page.getByRole("switch", { name: "Watch mode" }).click();
-
- // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to
- await page.waitForTimeout(3000);
- await modifyFile(ADDON_TEST_DEPENDENCY_PATH, (content) =>
- content.replace("test", "changed")
- );
-
- // Expect less than 10 tests to have run
- await expect(page.locator("#testing-module-description")).toContainText(
- /Ran \d tests/,
- { timeout: 30000 }
- );
-
- // Assert for expected failure
- const failingStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]'
- );
- await expect(failingStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Error"
- );
- });
-
- test("should run all tests in watch mode when the preview file's dependency is changed", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await page.getByRole("switch", { name: "Watch mode" }).click();
-
- // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to
- await page.waitForTimeout(3000);
- await modifyFile(PREVIEW_DEPENDENCY_PATH, (content) =>
- content.replace("Global Decorator", "Changed Decorator")
- );
-
- // Expect at least 20 tests to have run
- await expect(page.locator("#testing-module-description")).toContainText(
- /Ran [2-9]\d tests/,
- { timeout: 30000 }
- );
-
- // Assert for expected failure
- const failingStoryElement = page.locator(
- '[data-item-id="addons-group-test--expected-content"] [data-testid="tree-status-button"]'
- );
-
- await expect(failingStoryElement).toHaveAttribute(
- "aria-label",
- "Test status: Error"
- );
- });
-
- test("should run all tests in watch mode when the setup file's dependency is changed", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- // For whatever reason, sometimes it takes longer for the story to load
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- await page.getByRole("switch", { name: "Watch mode" }).click();
-
- // We shouldn't have to do an arbitrary wait, but because there is no UI for loading state yet, we have to
- await page.waitForTimeout(3000);
- await modifyFile(SETUP_FILE_DEPENDENCY_PATH, (content) =>
- content.replace("initial string", "changed string")
- );
-
- // Expect at least 20 tests to have run
- await expect(page.locator("#testing-module-description")).toContainText(
- /Ran [2-9]\d tests/,
- { timeout: 30000 }
- );
- });
-
- test("should collect coverage to testing module and HTML report", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Assert - No coverage report initially
- await expect(page.getByLabel("Open coverage report")).toHaveCount(0);
-
- // Act - Enable coverage and run tests
- await page.getByLabel("Coverage", { exact: true }).click();
- // Wait for Vitest to have (re)started
- await page.waitForTimeout(2000);
-
- await page.getByLabel("Start test run").click();
-
- // Assert - Coverage report is collected and shown
- await expect(page.getByLabel("Open coverage report")).toBeVisible({
- timeout: 30000,
- });
- const sbPercentageText = await page
- .getByLabel(/% coverage\)$/)
- .textContent();
- expect(sbPercentageText).toMatch(/^\d+%$/);
- const sbPercentage = Number.parseInt(
- sbPercentageText!.replace("%", "") ?? ""
- );
- expect(sbPercentage).toBeGreaterThanOrEqual(0);
- expect(sbPercentage).toBeLessThanOrEqual(100);
-
- // Act - Open HTML coverage report
- const coverageReportLink = await page.getByLabel("Open coverage report");
- // Remove target="_blank" attribute to open in the same tab
- await coverageReportLink.evaluate((elem) => elem.removeAttribute("target"));
- await page.getByLabel("Open coverage report").click();
-
- // Assert - HTML coverage report is accessible and reports the same coverage percentage as Storybook
- const htmlPercentageText =
- (await page
- .locator('span:has(+ :text("Statements"))')
- .first()
- .textContent()) ?? "";
- const htmlPercentage = Number.parseFloat(
- htmlPercentageText.replace("% ", "")
- );
- expect(Math.round(htmlPercentage)).toBe(sbPercentage);
-
- await page.goBack();
- });
-
- test("should run focused test for a single story", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Act - Open sidebar context menu, start focused test then close menu
- await page
- .locator('[data-item-id="addons-group-test--expected-failure"]')
- .hover();
- await page
- .locator(
- '[data-item-id="addons-group-test--expected-failure"] button[data-testid="context-menu"]'
- )
- .click();
- const sidebarContextMenu = page.getByRole("dialog");
- await sidebarContextMenu.getByLabel("Start test run").click();
-
- // Assert - Only one test is running and reported
- await expect(
- sidebarContextMenu.locator("#testing-module-description")
- ).toContainText("Ran 1 test", { timeout: 30000 });
- await expect(
- sidebarContextMenu.getByLabel("Component tests passed")
- ).toHaveCount(1);
- await page.click("body");
- await expect(
- page.locator(
- '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: Success"]'
- )
- ).toHaveCount(3); // 1 story, 1 component, 1 group
- });
-
- test("should show unhandled errors in the testing module", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(UNHANDLED_ERRORS_STORY_PATH, (content) =>
- setForceFailureFlag(content, true)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("example/unhandlederrors", "Success");
-
- const storyElement = sbPage.getCanvasBodyElement().getByText("Hello world");
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Act - Open sidebar context menu and start focused test
- await page.locator('[data-item-id="example-unhandlederrors"]').hover();
- await page
- .locator(
- '[data-item-id="example-unhandlederrors"] button[data-testid="context-menu"]'
- )
- .click();
- const sidebarContextMenu = page.getByRole("dialog");
- await sidebarContextMenu.getByLabel("Start test run").click();
-
- // HACK: the testing module popover has poor tracking of focus due to how many disabled
- // buttons it has and how deeply it changes its UI on events. This would be solved once
- // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab
- // around to reset focus.
- await page.keyboard.press("Tab");
- await page.keyboard.press("Escape");
- await page.click("body");
- await expect(sidebarContextMenu).not.toBeVisible();
-
- // Assert - Tests are running and errors are reported
- const errorLink = page.locator(
- "#storybook-testing-module #testing-module-description button"
- );
- await expect(errorLink).toContainText("View full error", {
- timeout: 30000,
- });
- await errorLink.click();
-
- await expect(page.locator("pre")).toContainText(
- "I THREW AN UNHANDLED ERROR!"
- );
- await expect(page.locator("pre")).toContainText("This error originated in");
- await expect(page.locator("pre")).toContainText(
- "The latest test that might've caused the error is"
- );
- await page.getByLabel("Close modal").click();
- });
-
- test("should run focused test for a component", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Act - Open sidebar context menu and start focused test
- await page.locator('[data-item-id="addons-group-test"]').hover();
- await page
- .locator(
- '[data-item-id="addons-group-test"] button[data-testid="context-menu"]'
- )
- .click();
- const sidebarContextMenu = page.getByRole("dialog");
- await sidebarContextMenu.getByLabel("Start test run").click();
-
- // Assert - Tests are running and reported
- await expect(
- sidebarContextMenu.locator("#testing-module-description")
- ).toContainText("Ran 9 tests", { timeout: 30000 });
- // Assert - Failing test shows as a failed status
- await expect(
- sidebarContextMenu.getByLabel("Component tests failed")
- ).toHaveCount(1);
- await expect(
- sidebarContextMenu.getByLabel(
- "Component tests failed (1 errors or warnings so far)"
- )
- ).toBeVisible();
-
- // HACK: the testing module popover has poor tracking of focus due to how many disabled
- // buttons it has and how deeply it changes its UI on events. This would be solved once
- // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab
- // around to reset focus.
- await page.keyboard.press("Tab");
- await page.keyboard.press("Escape");
- await page.click("body");
- await expect(sidebarContextMenu).not.toBeVisible();
-
- await page.click("body");
- await expect(
- page.locator(
- '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: Success"]'
- )
- ).toHaveCount(8);
- await expect(
- page.locator(
- '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: Error"]'
- )
- ).toHaveCount(3);
- });
-
- test("should run focused test for a group", async ({ page, browserName }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("addons/group/test", "Expected Failure");
-
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "test" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Act - Open sidebar context menu and start focused test
- await page.locator('[data-item-id="addons-group"]').hover();
- await page
- .locator(
- '[data-item-id="addons-group"] button[data-testid="context-menu"]'
- )
- .click();
- const sidebarContextMenu = page.getByRole("dialog");
- await sidebarContextMenu.getByLabel("Start test run").click();
-
- // Assert - 1 failing test shows as a failed status
- await expect(
- sidebarContextMenu.getByLabel(
- "Component tests failed (2 errors or warnings so far)"
- )
- ).toBeVisible();
- await expect(
- sidebarContextMenu.getByLabel("Component tests failed")
- ).toHaveCount(1);
-
- // HACK: the testing module popover has poor tracking of focus due to how many disabled
- // buttons it has and how deeply it changes its UI on events. This would be solved once
- // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab
- // around to reset focus.
- await page.keyboard.press("Tab");
- await page.keyboard.press("Escape");
- await page.click("body");
- await expect(sidebarContextMenu).not.toBeVisible();
-
- // Assert - Tests are running and reported
- await expect(page.locator("#testing-module-description")).toContainText(
- "Ran 11 tests",
- { timeout: 30000 }
- );
- await expect(
- page.locator(
- '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: Error"]'
- )
- ).toHaveCount(4); // 1 visible/expanded story, 1 expanded component, 1 collapsed component, 1 group
- });
-
- test("should run focused tests without coverage, even when enabled", async ({
- page,
- browserName,
- }) => {
- test.skip(browserName !== "chromium", `Skipping tests for ${browserName}`);
- // Arrange - Prepare Storybook
- await modifyFile(TEST_STORY_PATH, (content) =>
- setForceFailureFlag(content, false)
- );
-
- const sbPage = new SbPage(page, expect);
- await sbPage.navigateToStory("example/button", "CSF 3 Primary");
-
- const storyElement = sbPage
- .getCanvasBodyElement()
- .getByRole("button", { name: "foo" });
- await expect(storyElement).toBeVisible({ timeout: 30000 });
-
- // Act - Enable coverage
- await page.getByLabel("Coverage", { exact: true }).click();
- // Wait for Vitest to have (re)started
- await page.waitForTimeout(2000);
-
- // Act - Open sidebar context menu and start focused test
- await page
- .locator('[data-item-id="example-button--csf-3-primary"]')
- .hover();
- await page
- .locator(
- '[data-item-id="example-button--csf-3-primary"] button[data-testid="context-menu"]'
- )
- .click();
- const sidebarContextMenu = page.getByRole("dialog");
- await sidebarContextMenu.getByLabel("Start test run").click();
-
- // HACK: the testing module popover has poor tracking of focus due to how many disabled
- // buttons it has and how deeply it changes its UI on events. This would be solved once
- // we move to a declarative menu, and there's an ongoing PR for that. Until then, we tab
- // around to reset focus.
- await page.keyboard.press("Tab");
- await page.keyboard.press("Escape");
- await page.click("body");
- await expect(sidebarContextMenu).not.toBeVisible();
-
- // Arrange - Wait for test to finish and unfocus sidebar context menu
- await expect(page.locator("#testing-module-description")).toContainText(
- "Ran 1 test",
- { timeout: 30000 }
- );
- await page.click("body");
-
- // Assert - Coverage is not shown because Focused Tests shouldn't collect coverage
- await expect(page.getByLabel("Open coverage report")).not.toBeVisible();
-
- // Act - Run ALL tests
- await page.getByLabel("Start test run").click();
-
- // Arrange - Wait for tests to finish
- await expect(page.locator("#testing-module-description")).toContainText(
- /Ran \d{2,} tests/,
- { timeout: 30000 }
- );
-
- // Assert - Coverage percentage is now collected and shown because running all tests automatically re-enables coverage
- await expect(page.getByLabel("Open coverage report")).toBeVisible({
- timeout: 30000,
- });
- const sbPercentageText = await page
- .getByLabel(/% coverage\)$/)
- .textContent();
- expect(sbPercentageText).toMatch(/^\d+%$/);
- const sbPercentage = Number.parseInt(
- sbPercentageText!.replace("%", "") ?? ""
- );
- expect(sbPercentage).toBeGreaterThanOrEqual(0);
- expect(sbPercentage).toBeLessThanOrEqual(100);
- });
-
- test.fixme(
- "should still collect statuses even when the browser is closed",
- () => {}
- );
-
- test.fixme(
- "should have correct status count globally and in context menus",
- () => {}
- );
-
- test.fixme(
- "should open the correct component test and a11y panels when clicking on statuses",
- () => {}
- );
-});
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts
deleted file mode 100644
index 55be83eeeae8..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/composition.spec.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { expect, test } from '@playwright/test';
-
-import { SbPage } from '../../../../code/e2e-sandbox/util';
-
-const STORYBOOK_URL = 'http://localhost:6006';
-
-test.describe('composition', () => {
- // the composed storybook can be slow to load, so we need to increase the timeout
- test.describe.configure({ mode: 'serial', timeout: 60000, retries: 2 });
- test('should filter and render composed stories', async ({ page }) => {
- await page.goto(STORYBOOK_URL);
- await new SbPage(page, expect).waitUntilLoaded();
-
- // Expect that composed Storybooks are visible and loaded
- await expect(page.getByTitle('Storybook 8.0.0')).toBeVisible();
- await expect(page.locator('[id="storybook\\@8\\.0\\.0_components-badge"]')).toBeVisible({
- timeout: 15000,
- });
- await expect(page.getByTitle('Storybook 7.6.18')).toBeVisible();
- await expect(page.locator('[id="storybook\\@7\\.6\\.18_components-badge"]')).toBeVisible({
- timeout: 15000,
- });
-
- // Expect composed stories to be available in the sidebar
- await page.locator('[id="storybook\\@8\\.0\\.0_components-badge"]').click();
- await expect(
- page.locator('[id="storybook\\@8\\.0\\.0_components-badge--default"]')
- ).toBeVisible();
-
- await page.locator('[id="storybook\\@7\\.6\\.18_components-badge"]').click();
- await expect(
- page
- .locator('iframe[title="storybook-ref-storybook\\@7\\.6\\.18"]')
- .contentFrame()
- .locator('#storybook-root')
- .getByText('Default')
- ).toBeVisible({ timeout: 15000 });
-
- // Expect composed stories `to be available in the search
- await page.getByPlaceholder('Find components').fill('Button primary');
- await expect(
- page.getByRole('option', {
- name: 'Primary Storybook 7.6.18 / @components / Button',
- })
- ).toBeVisible();
-
- const buttonStory = page.getByRole('option', {
- name: 'Primary Storybook 8.0.0 / @blocks / examples / Button',
- });
- await expect(buttonStory).toBeVisible();
- await buttonStory.click();
-
- // Note: this could potentially be flaky due to it accessing a hosted Storybook
- await expect(
- page
- .locator('iframe[title="storybook-ref-storybook\\@8\\.0\\.0"]')
- .contentFrame()
- .getByRole('button')
- ).toBeVisible({ timeout: 15000 });
- });
-
- test('should filter and render composed stories on mobile', async ({ page }) => {
- page.setViewportSize({ width: 320, height: 800 });
- await page.goto(STORYBOOK_URL);
- await new SbPage(page, expect).waitUntilLoaded();
-
- await page.click('button[aria-label="Open navigation menu"]');
-
- // scroll down to the bottom of the element getByText('Skip to canvasStorybookSearch')
-
- await page.getByTitle('Storybook 7.6.18').scrollIntoViewIfNeeded();
-
- // Expect that composed Storybooks are visible
- await expect(page.getByTitle('Storybook 8.0.0')).toBeVisible();
- await expect(page.getByTitle('Storybook 7.6.18')).toBeVisible();
-
- // Expect composed stories to be available in the sidebar
- await expect(page.locator('[id="storybook\\@8\\.0\\.0_components-badge"]')).toBeVisible();
- await page.locator('[id="storybook\\@8\\.0\\.0_components-badge"]').click();
- await expect(
- page.locator('[id="storybook\\@8\\.0\\.0_components-badge--default"]')
- ).toBeVisible();
-
- await page.locator('[id="storybook\\@7\\.6\\.18_components-badge"]').click();
- await page.locator('[id="storybook\\@7\\.6\\.18_components-badge--default"]').click();
- await expect(
- page
- .locator('iframe[title="storybook-ref-storybook\\@7\\.6\\.18"]')
- .contentFrame()
- .locator('#storybook-root')
- .getByText('Default')
- ).toBeVisible({ timeout: 15000 });
-
- await page.click('button[aria-label="Open navigation menu"]');
-
- // Expect composed stories `to be available in the search
- await page.getByPlaceholder('Find components').fill('Button primary');
- await expect(
- page.getByRole('option', {
- name: 'Primary Storybook 7.6.18 / @components / Button',
- })
- ).toBeVisible();
-
- const buttonStory = page.getByRole('option', {
- name: 'Primary Storybook 8.0.0 / @blocks / examples / Button',
- });
- await expect(buttonStory).toBeVisible();
- await buttonStory.click();
-
- // Note: this could potentially be flaky due to it accessing a hosted Storybook
- await expect(
- page
- .locator('iframe[title="storybook-ref-storybook\\@8\\.0\\.0"]')
- .contentFrame()
- .getByRole('button')
- ).toBeVisible({ timeout: 15000 });
- });
-});
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts
deleted file mode 100644
index 6ceb1d0e89f5..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/save-from-controls.spec.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { expect, test } from "@playwright/test";
-import process from "process";
-
-import { SbPage } from "../../../../code/e2e-sandbox/util";
-
-const STORYBOOK_URL = "http://localhost:6006";
-const type = process.env.STORYBOOK_TYPE || "dev";
-
-test.describe("save-from-controls", () => {
- test.describe.configure({ mode: "serial" });
- test.skip(
- type === "build",
- `Skipping save-from-controls tests for production Storybooks`
- );
-
- test("Should be able to update a story", async ({ page, browserName }) => {
- // this is needed because the e2e test will generate a new file in the system
- // which we don't know of its location (it runs in different sandboxes)
- // so we just create a random id to make it easier to run tests
- const id = Math.random().toString(36).substring(7);
-
- test.skip(
- browserName !== "chromium",
- `Skipping save-from-controls tests for ${browserName}`
- );
-
- await page.goto(STORYBOOK_URL + "/?path=/story/example-mybutton--primary");
- const sbPage = new SbPage(page, expect);
- await sbPage.waitUntilLoaded();
-
- await sbPage.viewAddonPanel("Controls");
-
- // Update an arg
- const label = sbPage.panelContent().locator("textarea[name=children]");
- await label.fill(`"Updated ${id}"`);
- await label.blur();
-
- // Assert the footer is shown
- await sbPage
- .panelContent()
- .locator('[data-short-label="Unsaved changes"]')
- .isVisible();
-
- // update the story
- await sbPage
- .panelContent()
- .locator("button")
- .getByText("Update story")
- .click();
-
- // Assert the file is saved
- const notification1 = sbPage.page.getByTitle("Story saved");
- await expect(notification1).toBeVisible();
-
- // dismiss
- await notification1.click();
- await notification1.isHidden();
-
- // Update an arg
- const newStoryValue = `"Copied ${id}"`;
- await label.fill(newStoryValue);
- await label.blur();
-
- // Assert the footer is shown
- await sbPage
- .panelContent()
- .locator('[data-short-label="Unsaved changes"]')
- .isVisible();
-
- const buttons = sbPage
- .panelContent()
- .locator('[aria-label="Create new story with these settings"]');
-
- // clone the story
- await buttons.click();
-
- await sbPage.page
- .getByPlaceholder("Story export name")
- .fill("ClonedStory" + id);
- await sbPage.page.getByRole("button", { exact: true, name: "Create" }).click();
-
- // Assert the file is saved
- const notification2 = sbPage.page.getByTitle("Story created");
- await expect(notification2).toBeVisible();
- await notification2.click();
-
- // Assert the Button components is rendered in the preview
- await expect(sbPage.previewRoot()).toContainText(newStoryValue.replace(/"/g, ''));
- });
-});
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/package.json b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/package.json
deleted file mode 100644
index 86675ed07e8d..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/package.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "name": "portable-stories-react-vitest-3",
- "version": "0.0.0",
- "private": true,
- "scripts": {
- "build": "tsc && vite build",
- "dev": "vite",
- "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
- "playwright-e2e": "playwright test -c playwright-e2e.config.ts",
- "preview": "vite preview",
- "storybook": "NODE_OPTIONS=\"--preserve-symlinks --preserve-symlinks-main\" storybook dev -p 6006",
- "vitest": "SKIP_FAIL_ON_PURPOSE=true vitest run"
- },
- "resolutions": {
- "@playwright/test": "1.58.2",
- "@storybook/addon-a11y": "file:../../../code/addons/a11y",
- "@storybook/addon-docs": "file:../../../code/addons/docs",
- "@storybook/addon-jest": "file:../../../code/addons/jest",
- "@storybook/addon-links": "file:../../../code/addons/links",
- "@storybook/addon-themes": "file:../../../code/addons/themes",
- "@storybook/addon-vitest": "file:../../../code/addons/vitest",
- "@storybook/angular": "file:../../../code/frameworks/angular",
- "@storybook/builder-vite": "file:../../../code/builders/builder-vite",
- "@storybook/builder-webpack5": "file:../../../code/builders/builder-webpack5",
- "@storybook/codemod": "file:../../../code/lib/codemod",
- "@storybook/core-webpack": "portal:../../../code/lib/core-webpack",
- "@storybook/ember": "portal:../../../code/frameworks/ember",
- "@storybook/html": "file:../../../code/renderers/html",
- "@storybook/nextjs": "file:../../../code/frameworks/nextjs",
- "@storybook/preact": "file:../../../code/renderers/preact",
- "@storybook/preact-vite": "file:../../../code/frameworks/preact-vite",
- "@storybook/preset-create-react-app": "file:../../../code/presets/create-react-app",
- "@storybook/preset-react-webpack": "file:../../../code/presets/react-webpack",
- "@storybook/preset-server-webpack": "file:../../../code/presets/server-webpack",
- "@storybook/react": "file:../../../code/renderers/react",
- "@storybook/react-dom-shim": "file:../../../code/lib/react-dom-shim",
- "@storybook/react-vite": "file:../../../code/frameworks/react-vite",
- "@storybook/react-webpack5": "file:../../../code/frameworks/react-webpack5",
- "@storybook/server": "file:../../../code/renderers/server",
- "@storybook/server-webpack5": "file:../../../code/frameworks/server-webpack5",
- "@storybook/svelte": "file:../../../code/renderers/svelte",
- "@storybook/svelte-vite": "file:../../../code/frameworks/svelte-vite",
- "@storybook/sveltekit": "file:../../../code/frameworks/sveltekit",
- "@storybook/vue3": "file:../../../code/renderers/vue3",
- "@storybook/vue3-vite": "file:../../../code/frameworks/vue3-vite",
- "@storybook/web-components": "file:../../../code/renderers/web-components",
- "@storybook/web-components-vite": "file:../../../code/frameworks/web-components-vite",
- "eslint-plugin-storybook": "file:../../../code/lib/eslint-plugin",
- "playwright": "1.58.2",
- "storybook": "portal:../../../code/core"
- },
- "dependencies": {
- "react": "^18.0.0",
- "react-dom": "^18.0.0"
- },
- "devDependencies": {
- "@playwright/test": "1.58.2",
- "@storybook/addon-a11y": "^8.0.0",
- "@storybook/addon-vitest": "^8.0.0",
- "@storybook/react": "^8.0.0",
- "@storybook/react-vite": "^8.0.0",
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/react": "^16.2.0",
- "@types/identity-obj-proxy": "^3",
- "@types/react": "^19.0.8",
- "@types/react-dom": "^19.0.3",
- "@typescript-eslint/eslint-plugin": "^6.21.0",
- "@typescript-eslint/parser": "^6.21.0",
- "@vitejs/plugin-react": "^4.2.1",
- "@vitest/browser": "^3.2.4",
- "@vitest/coverage-v8": "^3.2.4",
- "@vitest/ui": "^3.2.4",
- "eslint": "^8.56.0",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.5",
- "eslint-plugin-storybook": "^0.11.4",
- "identity-obj-proxy": "^3.0.0",
- "storybook": "^8.0.0",
- "typescript": "^5.8.3",
- "vite": "^5.1.1",
- "vitest": "^3.2.4"
- },
- "nx": {
- "includedScripts": []
- }
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts
deleted file mode 100644
index c1b06b44c09b..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/playwright-e2e.config.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-import path from "node:path";
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-export default defineConfig({
- testDir: "./e2e-tests",
- outputDir: "./test-results",
- /* Maximum time one test can run for. */
- timeout: (process.env.CI ? 60 : 30) * 1000,
- /* Run tests in files in parallel */
- fullyParallel: false,
-
- /* Fail the build on CI if you accidentally left test.only in the source code. */
- forbidOnly: !!process.env.CI,
- retries: 0,
- /* Run tests serially to avoid side effects */
- workers: 1,
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
- reporter: [
- ["line"],
- [
- "junit",
- {
- embedAnnotationsAsProperties: true,
- outputFile: path.join(__dirname, "..", "..", "..", "test-results", "react-vitest3-e2e-ui.xml"),
- },
- ],
- ],
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: "retain-on-failure",
- // video: "retain-on-failure",
- // headless: false,
- },
-
- /* Configure projects for major browsers */
- projects: [
- {
- name: "chromium",
- use: { ...devices["Desktop Chrome"] },
- },
- // {
- // name: 'firefox',
- // use: { ...devices['Desktop Firefox'] },
- // },
- // {
- // name: 'webkit',
- // use: { ...devices['Desktop Safari'] },
- // },
- ],
-
- webServer: {
- command: "yarn storybook",
- url: "http://127.0.0.1:6006",
- reuseExistingServer: true,
- },
-});
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js
deleted file mode 100644
index a684b479b9ce..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/pre-e2e.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable no-undef */
-/* eslint-disable @typescript-eslint/no-var-requires */
-const fs = require("node:fs");
-const path = require("node:path");
-
-const testStoryPath = path.resolve("stories/AddonTest.stories.tsx");
-
-console.log(`Pre-e2e script: clearing ${testStoryPath}`);
-const storyContent = fs.readFileSync(testStoryPath).toString();
-fs.writeFileSync(testStoryPath, storyContent.replace("forceFailure: true", "forceFailure: false"));
\ No newline at end of file
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/project.json b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/project.json
deleted file mode 100644
index d1879b795c01..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/project.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "portable-stories-kitchen-sink/react-vitest-3",
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "projectType": "application",
- "targets": {
- "e2e-ui": {}
- },
- "tags": ["ci:normal", "ci:merged", "ci:daily"]
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx
deleted file mode 100644
index fcb59a0ff94e..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.stories.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { expect, within } from 'storybook/test';
-import { Meta, type StoryObj } from '@storybook/react-vite';
-import { instrument } from 'storybook/internal/instrumenter';
-import { Component } from './AddonTest';
-
-declare global {
- // eslint-disable-next-line no-var
- var __vitest_browser__: boolean;
-}
-
-const meta = {
- title: 'Addons/Group/Test',
- component: Component,
-} as Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-const { pass } = instrument({
- pass: async () => {},
-}, { intercept: true })
-
-export const ExpectedFailure: Story = {
- args: {
- forceFailure: false,
- },
- play: async ({ args }) => {
- await pass();
- if(args.forceFailure) {
- throw new Error('Expected failure');
- }
- }
-};
-
-export const ExpectedSuccess: Story = {
- play: async () => {
- await pass();
- }
-};
-
-export const ExpectedContent: Story = {
- play: async ({ canvasElement }) => {
- const button = within(canvasElement).getByRole('button');
- await expect(button).toHaveTextContent('test');
- const decoratorString = within(canvasElement).getByTestId('decorator-string');
- await expect(decoratorString).toHaveTextContent('Global Decorator');
- }
-};
-
-export const LongRunning: Story = {
- loaders: [async () => new Promise((resolve) => setTimeout(resolve, 800))],
-};
-
-// Tests will pass in browser, but fail in CLI
-export const MismatchFailure: Story = {
- play: async () => {
- await pass();
- if(!globalThis.__vitest_browser__) {
- throw new Error('Expected failure');
- }
- }
-};
-
-// Tests will fail in browser, but pass in CLI
-export const MismatchSuccess: Story = {
- play: async () => {
- await pass();
- if(globalThis.__vitest_browser__) {
- throw new Error('Unexpected success');
- }
- },
- tags: ['fail-on-purpose'],
-};
-
-export const PreviewHeadTest: Story = {
- play: async () => {
- const styles = window.getComputedStyle(document.body);
- // set in preview-head.html
- expect(styles.backgroundColor).toBe('rgb(250, 250, 210)');
- // set in main.js#previewHead
- expect(styles.borderColor).toBe('rgb(255, 0, 0)');
- }
-};
-
-export const StaticDirTest: Story = {
- play: async () => {
- const path = '/test-static-dirs/static.js';
- const { staticFunction } = await import(/* @vite-ignore */path);
- expect(staticFunction()).toBe(true);
- }
-}
-
-export const ViteFinalTest: Story = {
- play: async () => {
- // @ts-expect-error TS doesn't know about the alias
- const { aliasedFunction } = await import('test-alias');
- expect(aliasedFunction()).toBe(true);
- }
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.tsx
deleted file mode 100644
index 01850675e5c7..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/AddonTest.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { getButtonString } from './get-button-string';
-
-export const Component = () => {
- return ;
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx
deleted file mode 100644
index c40e163b0d32..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.stories.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { expect, fn } from 'storybook/test';
-import type { Meta, StoryFn as CSF2Story, StoryObj as CSF3Story } from '@storybook/react-vite';
-
-import type { ButtonProps } from './Button';
-import { Button } from './Button';
-
-const meta = {
- title: 'Example/Button',
- component: Button,
- argTypes: {
- backgroundColor: { control: 'color' },
- },
-} satisfies Meta;
-
-export default meta;
-
-const Template: CSF2Story = (args) => ;
-
-export const CSF2Secondary = Template.bind({});
-CSF2Secondary.args = {
- children: 'Children coming from story args!',
- primary: false,
-};
-
-const getCaptionForLocale = (locale: string) => {
- switch (locale) {
- case 'es':
- return 'Hola!';
- case 'fr':
- return 'Bonjour!';
- case 'kr':
- return '안녕하세요!';
- case 'pt':
- return 'Olá!';
- case 'en':
- return 'Hello!';
- default:
- return undefined;
- }
-};
-
-export const CSF2StoryWithLocale: CSF2Story = (args, { globals: { locale } }) => {
- const caption = getCaptionForLocale(locale);
- return (
- <>
- locale: {locale}
-
- >
- );
-};
-CSF2StoryWithLocale.storyName = 'WithLocale';
-
-export const CSF2StoryWithParamsAndDecorator: CSF2Story = (args) => {
- return ;
-};
-CSF2StoryWithParamsAndDecorator.args = {
- children: 'foo',
-};
-CSF2StoryWithParamsAndDecorator.parameters = {
- layout: 'centered',
-};
-CSF2StoryWithParamsAndDecorator.decorators = [(StoryFn) => ];
-
-export const CSF3Primary: CSF3Story = {
- args: {
- children: "foo",
- size: 'large',
- primary: true,
- },
- // Accessibility is failing for the Button
- tags: ['fail-on-purpose'],
-};
-
-export const CSF3Button: CSF3Story = {
- args: { children: 'foo' },
-};
-
-export const CSF3ButtonWithRender: CSF3Story = {
- ...CSF3Button,
- render: (args) => (
-
-
I am a custom render function
-
-
- ),
-};
-
-const mockFn = fn();
-export const WithLoader: CSF3Story<{ mockFn: (val: string) => string }> = {
- args: {
- mockFn,
- },
- loaders: [
- async () => {
- mockFn.mockReturnValueOnce('mockFn return value');
- return {
- value: 'loaded data',
- };
- },
- ],
- render: (args, { loaded }) => {
- const data = args.mockFn('render');
- return (
-
-
{loaded.value}
-
{String(data)}
-
- );
- },
- play: async () => {
- expect(mockFn).toHaveBeenCalledWith('render');
- },
-};
\ No newline at end of file
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx
deleted file mode 100644
index 8f5d125bb293..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/Button.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import './button.css';
-
-export interface ButtonProps {
- /**
- * Is this the principal call to action on the page?
- */
- primary?: boolean;
- /**
- * What background color to use
- */
- backgroundColor?: string;
- /**
- * How large should the button be?
- */
- size?: 'small' | 'medium' | 'large';
- /**
- * Button contents
- */
- children: React.ReactNode;
- /**
- * Optional click handler
- */
- onClick?: () => void;
-}
-
-/**
- * Primary UI component for user interaction
- */
-export const Button: React.FC = (props) => {
- const { primary = false, size = 'medium', backgroundColor, children, ...otherProps } = props;
- const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
- console.log({props})
- return (
-
- );
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/MyButton.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/MyButton.stories.tsx
deleted file mode 100644
index d79c54c2f1c0..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/MyButton.stories.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Meta, StoryObj as CSF3Story } from '@storybook/react-vite';
-
-import type { ButtonProps } from './Button';
-import { Button } from './Button';
-
-const meta = {
- title: 'Example/MyButton',
- component: Button,
- tags: ['!test'],
- argTypes: {
- backgroundColor: { control: 'color' },
- },
-} satisfies Meta;
-
-export default meta;
-
-export const Primary: CSF3Story = {
- args: { children: 'foo' },
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/OtherComponent.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/OtherComponent.stories.tsx
deleted file mode 100644
index cf35c3f8e98e..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/OtherComponent.stories.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Meta, type StoryObj } from '@storybook/react-vite';
-
-const Component = () =>
-
-const meta = {
- title: 'Addons/Group/Other',
- component: Component,
-} as Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Passes: Story = {
-};
-
-export const Fails: Story = {
- play: async () => {
- throw new Error('Expected failure');
- },
- tags: ['fail-on-purpose'],
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/UnhandledErrors.stories.tsx b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/UnhandledErrors.stories.tsx
deleted file mode 100644
index 49a5db3a6ba0..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/UnhandledErrors.stories.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-vite';
-
-async function unhandledRejection() {
- throwError('I THREW AN UNHANDLED REJECTION!');
-}
-
-function unhandledError() {
- throwError('I THREW AN UNHANDLED ERROR!');
-}
-
-function throwError(message: string) {
- throw new Error(message);
-}
-const meta = {
- title: 'Example/UnhandledErrors',
- args: {
- errorType: null,
- forceFailure: false,
- },
- component: ({ errorType, forceFailure }) => {
- if (forceFailure) {
- if (errorType === 'rejection') {
- setTimeout(unhandledRejection, 0);
- } else if (errorType === 'error') {
- setTimeout(unhandledError, 0);
- }
- }
- return 'Hello world';
- },
-} as Meta<{ errorType: 'rejection' | 'error' | null; forceFailure?: boolean }>;
-export default meta;
-type Story = StoryObj;
-
-export const UnhandledError: Story = {
- args: {
- errorType: 'error',
- },
-};
-
-export const UnhandledRejection: Story = {
- args: {
- errorType: 'rejection',
- },
-};
-
-export const Success: Story = {};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/button.css b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/button.css
deleted file mode 100644
index dc91dc76370b..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/button.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.storybook-button {
- font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
- font-weight: 700;
- border: 0;
- border-radius: 3em;
- cursor: pointer;
- display: inline-block;
- line-height: 1;
-}
-.storybook-button--primary {
- color: white;
- background-color: #1ea7fd;
-}
-.storybook-button--secondary {
- color: #333;
- background-color: transparent;
- box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
-}
-.storybook-button--small {
- font-size: 12px;
- padding: 10px 16px;
-}
-.storybook-button--medium {
- font-size: 14px;
- padding: 11px 20px;
-}
-.storybook-button--large {
- font-size: 16px;
- padding: 12px 24px;
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts
deleted file mode 100644
index 7c9d9ab153d0..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/stories/get-button-string.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const getButtonString = () => {
- return "test";
-};
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.json b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.json
deleted file mode 100644
index 0bcaa35b673d..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
- },
- "include": ["stories", "e2e-tests"],
- "references": [{ "path": "./tsconfig.node.json" }]
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.node.json b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.node.json
deleted file mode 100644
index 97ede7ee6f2d..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/tsconfig.node.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "composite": true,
- "skipLibCheck": true,
- "module": "ESNext",
- "moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true,
- "strict": true
- },
- "include": ["vite.config.ts"]
-}
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts
deleted file mode 100644
index 9cc50ead1c0a..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vite.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-
-// https://vitejs.dev/config/
-export default defineConfig({
- plugins: [react()],
-});
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts
deleted file mode 100644
index d09ebcb58a9b..000000000000
--- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/vitest.workspace.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { defineWorkspace } from "vitest/config";
-import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
-
-export default defineWorkspace([
- {
- extends: "vite.config.ts",
- plugins: [
- storybookTest(
- process.env.SKIP_FAIL_ON_PURPOSE
- ? {
- tags: {
- exclude: ["fail-on-purpose"],
- },
- }
- : undefined
- ),
- ],
- test: {
- name: "storybook",
- pool: "threads",
- deps: {
- optimizer: {
- web: {
- enabled: false,
- },
- },
- },
- browser: {
- enabled: true,
- provider: "playwright",
- headless: true,
- instances: [
- {
- browser: "chromium",
- },
- ],
- },
- setupFiles: ["./.storybook/vitest.setup.ts"],
- environment: "jsdom",
- },
- },
-]);
diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/yarn.lock
deleted file mode 100644
index e69de29bb2d1..000000000000