Vitest 中的可攜式 story
如果您使用實驗性的 CSF Factories 格式,則無需使用可攜式 stories API。相反地,您可以直接匯入和使用您的 stories。
可攜式 stories 是 Storybook stories,可以在外部環境中使用,例如 Vitest。
通常,Storybook 會自動組合 story 及其註解,作為story 管線的一部分。在 Vitest 測試中使用 stories 時,您必須自行處理 story 管線,這正是 composeStories
和 composeStory
函數的功能。
此處指定的 API 在 Storybook 8.2.7
及更高版本中可用。如果您使用的是較舊版本的 Storybook,可以升級到最新版本 (npx storybook@latest upgrade
) 以使用此 API。如果您無法升級,可以使用先前的 API,該 API 使用 .play()
方法而不是 .run()
,但在其他方面是相同的。
使用 Next.js
嗎? 您可以透過安裝和設定 @storybook/experimental-nextjs-vite
來使用 Vitest 測試您的 Next.js stories,該套件重新匯出 vite-plugin-storybook-nextjs 套件。
composeStories
composeStories
將處理您指定的元件 stories,將它們各自與必要的註解組合,並傳回包含組合 stories 的物件。
預設情況下,組合 story 將使用 story 中定義的 args 渲染元件。您也可以在測試中將任何 props 傳遞給元件,這些 props 將覆寫 story 的 args 中傳遞的值。
import { test, expect } from 'vitest';
import { screen } from '@testing-library/react';
// 👉 Using Next.js? Import from @storybook/nextjs instead
import { composeStories } from '@storybook/react';
// Import all stories and the component annotations from the stories file
import * as stories from './Button.stories';
// Every component that is returned maps 1:1 with the stories,
// but they already contain all annotations from story, meta, and project levels
const { Primary, Secondary } = composeStories(stories);
test('renders primary button with default args', async () => {
await Primary.run();
const buttonElement = screen.getByText('Text coming from args in stories file!');
expect(buttonElement).not.toBeNull();
});
test('renders primary button with overridden props', async () => {
// You can override props by passing them in the context argument of the run function
await Primary.run({ args: { ...Primary.args, children: 'Hello world' } });
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});
類型
(
csfExports: CSF file exports,
projectAnnotations?: ProjectAnnotations
) => Record<string, ComposedStoryFn>
Parameters
csfExports
(必要)
類型:CSF 檔案匯出
指定您要組合哪個元件的 stories。傳遞來自 CSF 檔案的完整匯出集合 (而非預設匯出!)。例如 import * as stories from './Button.stories'
projectAnnotations
類型:ProjectAnnotation | ProjectAnnotation[]
指定要套用至組合 stories 的專案註解。
提供此參數是為了方便起見。您可能應該改用 setProjectAnnotations
。有關 ProjectAnnotation
類型的詳細資訊,請參閱該函數的 projectAnnotations
參數。
此參數可用於覆寫透過 setProjectAnnotations
套用的專案註解。
傳回
類型:Record<string, ComposedStoryFn>
一個物件,其中鍵是 stories 的名稱,值是組合 stories。
此外,組合 story 將具有以下屬性
屬性 | 類型 | 描述 |
---|---|---|
args | Record<string, any> | story 的 args |
argTypes | ArgType | story 的 argTypes |
id | string | story 的 id |
parameters | Record<string, any> | story 的 parameters |
play | (context) => Promise<void> | undefined | 執行給定 story 的 play 函數 |
run | (context) => Promise<void> | undefined | 掛載並執行給定 story 的 play 函數 |
storyName | string | story 的名稱 |
tags | string[] | story 的 tags |
composeStory
如果您希望為元件組合單一 story,可以使用 composeStory
。
import { vi, test, expect } from 'vitest';
import { screen } from '@testing-library/react';
import { composeStory } from '@storybook/react';
import meta, { Primary as PrimaryStory } from './Button.stories';
// Returns a story which already contains all annotations from story, meta and global levels
const Primary = composeStory(PrimaryStory, meta);
test('renders primary button with default args', async () => {
await Primary.run();
const buttonElement = screen.getByText('Text coming from args in stories file!');
expect(buttonElement).not.toBeNull();
});
test('renders primary button with overridden props', async () => {
await Primary.run({ args: { ...Primary.args, label: 'Hello world' } });
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});
類型
(
story: Story export,
componentAnnotations: Meta,
projectAnnotations?: ProjectAnnotations,
exportsName?: string
) => ComposedStoryFn
Parameters
story
(必要)
類型:Story export
指定您要組合哪個 story。
componentAnnotations
(必要)
類型:Meta
來自 stories 檔案的預設匯出,包含 story
。
projectAnnotations
類型:ProjectAnnotation | ProjectAnnotation[]
指定要套用至組合 story 的專案註解。
提供此參數是為了方便起見。您可能應該改用 setProjectAnnotations
。有關 ProjectAnnotation
類型的詳細資訊,請參閱該函數的 projectAnnotations
參數。
此參數可用於覆寫透過 setProjectAnnotations
套用的專案註解。
exportsName
類型:string
您可能不需要這個。由於 composeStory
接受單一 story,因此它無法存取該 story 匯出在檔案中的名稱 (如 composeStories
)。如果您必須確保測試中的 story 名稱是唯一的,並且無法使用 composeStories
,則可以在此處傳遞 story 匯出的名稱。
傳回
類型:ComposedStoryFn
單一組合 story。
setProjectAnnotations
此 API 應在測試執行之前呼叫一次,通常在設定檔案中。這將確保在呼叫 composeStories
或 composeStory
時,也會考慮專案註解。
這些是在設定檔案中需要的組態
- 預覽註解:在
.storybook/preview.ts
中定義的那些 - 附加元件註解 (選用):由附加元件匯出的那些
- beforeAll:在所有測試之前執行的程式碼 (更多資訊)
import { beforeAll } from 'vitest';
// 👇 If you're using Next.js, import from @storybook/nextjs
// If you're using Next.js with Vite, import from @storybook/experimental-nextjs-vite
import { setProjectAnnotations } from '@storybook/react';
// 👇 Import the exported annotations, if any, from the addons you're using; otherwise remove this
import * as addonAnnotations from 'my-addon/preview';
import * as previewAnnotations from './.storybook/preview';
const annotations = setProjectAnnotations([previewAnnotations, addonAnnotations]);
// Run Storybook's beforeAll hook
beforeAll(annotations.beforeAll);
有時,story 可能需要附加元件的 decorator 或 loader 才能正確渲染。例如,附加元件可以套用一個 decorator,將您的 story 包裝在必要的路由器上下文中。在這種情況下,您必須在設定的專案註解中包含該附加元件的 preview
匯出。請參閱上面範例中的 addonAnnotations
。
注意:如果附加元件不會自動套用 decorator 或 loader 本身,而是匯出它們供您在 .storybook/preview.js|ts
中手動套用 (例如,使用來自 @storybook/addon-themes 的 withThemeFromJSXProvider
),那麼您無需執行任何其他操作。它們已包含在上面範例中的 previewAnnotations
中。
如果您需要設定 Testing Library 的 render
或使用不同的渲染函數,請在此討論中告訴我們,以便我們可以更瞭解您的需求。
類型
(projectAnnotations: ProjectAnnotation | ProjectAnnotation[]) => ProjectAnnotation
Parameters
projectAnnotations
(必要)
類型:ProjectAnnotation | ProjectAnnotation[]
一組專案註解 (在 .storybook/preview.js|ts
中定義的那些) 或一組專案註解的陣列,將套用至所有組合 stories。
註解
註解是套用至 story 的 metadata,例如 args、decorators、loaders 和 play 函數。它們可以針對特定 story、元件的所有 stories 或專案中的所有 stories 定義。
Story 管線
為了在 Storybook 中預覽您的 stories,Storybook 會執行 story 管線,其中包括套用專案註解、載入資料、渲染 story 和播放互動。這是管線的簡化版本
但是,當您想要在不同的環境中重複使用 story 時,務必了解所有這些步驟構成了一個 story。可攜式 stories API 為您提供了在外部環境中重新建立該 story 管線的機制
1. 套用專案層級註解
註解來自 story 本身、該 story 的元件和專案。專案層級註解是在您的 .storybook/preview.js
檔案中以及由您使用的附加元件定義的那些。在可攜式 stories 中,這些註解不會自動套用 — 您必須自行套用它們。
👉 為此,您可以使用 setProjectAnnotations
API。
2. 組合
透過執行 composeStories
或 composeStory
來準備 story。結果是一個可渲染的元件,代表 story 的渲染函數。
3. 執行
最後,stories 可以在渲染之前準備它們需要的資料 (例如,設定一些模擬或提取資料),方法是定義 loaders、beforeEach 或在使用 mount 時讓所有 story 程式碼都在 play 函數中。在可攜式 stories 中,當您呼叫組合 story 的 run
方法時,將執行所有這些步驟。
👉 為此,您可以使用 composeStories
或 composeStory
API。組合 story 將傳回要呼叫的 run
方法。
import { test } from 'vitest';
import { composeStories } from '@storybook/react';
import * as stories from './Button.stories';
const { Primary } = composeStories(stories);
test('renders and executes the play function', async () => {
// Mount story and run interactions
await Primary.run();
});
如果您的 play 函數包含 assertions (例如 expect
呼叫),當這些 assertions 失敗時,您的測試將會失敗。
覆寫全域變數
如果您的 stories 根據全域變數的行為有所不同 (例如,以英文或西班牙文渲染文字),您可以在組合 story 時透過覆寫專案註解,在可攜式 stories 中定義這些全域值
import { test } from 'vitest';
import { render } from '@testing-library/react';
import { composeStory } from '@storybook/react';
import meta, { Primary as PrimaryStory } from './Button.stories';
test('renders in English', async () => {
const Primary = composeStory(
PrimaryStory,
meta,
{ globals: { locale: 'en' } }, // 👈 Project annotations to override the locale
);
await Primary.run();
});
test('renders in Spanish', async () => {
const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } });
await Primary.run();
});