Vitest 中的可攜式 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,將每個 story 與必要的註釋組成,並傳回包含組成 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 play 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>
參數
csfExports
(必要)
類型:CSF 檔案匯出
指定您想要組成的元件 stories。從 CSF 檔案傳遞整組匯出(而不是預設匯出!)。例如:import * as stories from './Button.stories'
projectAnnotations
類型:ProjectAnnotation | ProjectAnnotation[]
指定要套用至組成的 stories 的專案註釋。
為了方便起見,提供了此參數。您應該改用 setProjectAnnotations
。有關 ProjectAnnotation
類型的詳細資訊,請參閱該函式的 projectAnnotations
參數。
此參數可用於覆寫透過 setProjectAnnotations
套用的專案註釋。
傳回
類型:Record<string, ComposedStoryFn>
物件,其中鍵是 stories 的名稱,而值是組成的 stories。
此外,組合後的故事將具有以下屬性
屬性 | 類型 | 描述 |
---|---|---|
args | Record<string, any> | 故事的 args |
argTypes | ArgType | 故事的 argTypes |
id | string | 故事的 id |
parameters | Record<string, any> | 故事的 parameters |
play | (context) => Promise<void> | undefined | 執行給定故事的 play 函式 |
run | (context) => Promise<void> | undefined | 掛載並執行 給定故事的 play 函式 |
storyName | string | 故事的名稱 |
tags | string[] | 故事的 tags |
composeStory
如果您希望為元件組合單一故事,可以使用 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
參數
story
(必要)
類型:Story export
指定您要組合的故事。
componentAnnotations
(必要)
類型:Meta
包含 story
的故事檔案中的預設匯出。
projectAnnotations
類型:ProjectAnnotation | ProjectAnnotation[]
指定要套用至組合後故事的專案註解。
為了方便起見,提供了此參數。您應該改用 setProjectAnnotations
。有關 ProjectAnnotation
類型的詳細資訊,請參閱該函式的 projectAnnotations
參數。
此參數可用於覆寫透過 setProjectAnnotations
套用的專案註釋。
exportsName
類型:string
您可能不需要這個。因為 composeStory
接受單一故事,它無法存取該故事在檔案中的匯出名稱(例如 composeStories
)。如果您必須確保測試中故事名稱的唯一性,而且您無法使用 composeStories
,則可以在這裡傳遞故事匯出的名稱。
傳回
類型:ComposedStoryFn
單一的 組合故事。
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);
有時,故事可能需要外掛程式的裝飾器或載入器才能正確呈現。例如,外掛程式可以套用一個裝飾器,將您的故事包裝在必要的路由器上下文中。在這種情況下,您必須將該外掛程式的 preview
匯出包含在設定的專案註解中。請參閱上面範例中的 addonAnnotations
。
注意:如果外掛程式本身不會自動套用裝飾器或載入器,而是將它們匯出以供您在 .storybook/preview.js|ts
中手動套用(例如,使用來自 @storybook/addon-themes 的 withThemeFromJSXProvider
),則您不需要執行其他任何操作。它們已包含在上述範例中的 previewAnnotations
中。
如果您需要設定 Testing Library 的 render
或使用不同的 render 函式,請在此討論中告訴我們,以便我們進一步了解您的需求。
類型
(projectAnnotations: ProjectAnnotation | ProjectAnnotation[]) => ProjectAnnotation
參數
projectAnnotations
(必要)
類型:ProjectAnnotation | ProjectAnnotation[]
一組專案註解(在 .storybook/preview.js|ts
中定義的註解)或一組專案註解的陣列,將套用至所有組合後的故事。
註解
註解是套用至故事的中繼資料,例如 args、裝飾器、載入器和 play 函式。它們可以為特定故事、元件的所有故事或專案中的所有故事定義。
故事管線
為了在 Storybook 中預覽您的故事,Storybook 會執行一個故事管線,其中包括套用專案註解、載入資料、呈現故事和播放互動。這是管線的簡化版本
但是,當您想要在不同的環境中重複使用故事時,了解所有這些步驟如何構成故事至關重要。可攜式故事 API 為您提供在外部環境中重新建立該故事管線的機制
1. 套用專案層級註解
註解來自故事本身、該故事的元件和專案。專案層級註解是在您的 .storybook/preview.js
檔案中以及您正在使用的外掛程式中定義的註解。在可攜式故事中,這些註解不會自動套用 — 您必須自行套用它們。
👉 為此,您可以使用 setProjectAnnotations
API。
2. 組合
這個故事是透過執行 composeStories
或 composeStory
來準備的。結果是一個可渲染的元件,代表故事的渲染函式。
3. 執行
最後,故事可以在渲染之前,透過定義 loaders、beforeEach 或在使用 mount 時將所有故事程式碼放在 play 函式中,來準備它們需要的資料(例如,設定一些 mock 或獲取資料)。在可攜式故事中,當您呼叫已組合故事的 run
方法時,將會執行所有這些步驟。
👉 為此,您可以使用 composeStories
或 composeStory
API。已組合的故事將會回傳一個 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 函式包含斷言(例如 expect
呼叫),當這些斷言失敗時,您的測試將會失敗。
覆寫全域設定
如果您的故事根據 全域設定 有不同的行為(例如,以英文或西班牙文渲染文字),您可以在組合故事時覆寫專案註解,以在可攜式故事中定義這些全域值
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();
});