From zenbu-powers
當在 React IT Gherkin 中撰寫使用者互動步驟(click, type, submit)時, 「只能」使用此指令。使用 @testing-library/user-event 模擬使用者操作。
npx claudepluginhub zenbuapps/zenbu-powers --plugin zenbu-powersThis skill uses the workspace's default tool permissions.
Given/When 語句執行**寫入操作**(Command)。
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Share bugs, ideas, or general feedback.
Given/When 語句執行寫入操作(Command)。
識別規則:
通用判斷:如果語句是修改系統狀態的操作且需要透過 UI 互動完成,就使用此 Handler。
| Command | Query | |
|---|---|---|
| 觸發方式 | user-event click/type/submit | Component render + data fetch |
| 系統狀態 | 修改(POST/PUT/DELETE 被觸發) | 不修改(GET 被觸發) |
| Response 驗證 | 不驗證(交給 Then) | 不驗證(交給 Then) |
| Given + Command | When + Command | |
|---|---|---|
| 目的 | 建立前置條件(透過 UI 完成某操作) | 測試目標操作 |
| 失敗處理 | 不預期失敗(前置操作必須成功) | 可能成功或失敗 |
| 等待策略 | await waitFor() 確認操作完成 | 執行後不立即驗證 |
userEvent instance(userEvent.setup() 或 createUser())getByRole)user.click(), user.type(), user.clear(), user.selectOptions())user.click(submitButton) 或 user.keyboard('{Enter}')waitFor 等待 loading 狀態消失)When 用戶 "Alice" 更新課程 1 的影片進度為 80%
import { screen, waitFor } from '@testing-library/react';
import { createUser } from '@/test/helpers/user-event';
const user = createUser();
// 找到輸入欄位並填寫
const progressInput = screen.getByRole('spinbutton', { name: /進度/i });
await user.clear(progressInput);
await user.type(progressInput, '80');
// 點擊提交
const submitButton = screen.getByRole('button', { name: /更新|送出/i });
await user.click(submitButton);
// 等待 API 呼叫完成(loading 狀態消失)
await waitFor(() => {
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
// 不做 assertion — 驗證交給 Then handler
When 用戶 "Alice" 建立新課程:名稱 "React 進階",價格 2000
const user = createUser();
await user.type(screen.getByRole('textbox', { name: /名稱/i }), 'React 進階');
await user.type(screen.getByRole('spinbutton', { name: /價格/i }), '2000');
await user.click(screen.getByRole('button', { name: /建立/i }));
When 用戶 "Alice" 將訂單狀態改為 "已付款"
const user = createUser();
const select = screen.getByRole('combobox', { name: /狀態/i });
await user.selectOptions(select, 'PAID');
await user.click(screen.getByRole('button', { name: /確認/i }));
When 用戶 "Alice" 刪除課程 1
const user = createUser();
const deleteButton = screen.getByRole('button', { name: /刪除/i });
await user.click(deleteButton);
// 如果有確認對話框
const confirmButton = screen.getByRole('button', { name: /確認刪除/i });
await user.click(confirmButton);
When 用戶 "Alice" 批量更新以下商品數量:
| productId | quantity |
| PROD-001 | 3 |
| PROD-002 | 1 |
const user = createUser();
// 找到第一個商品的數量輸入
const rows = screen.getAllByRole('row');
const firstRow = rows[1]; // skip header
const firstInput = within(firstRow).getByRole('spinbutton');
await user.clear(firstInput);
await user.type(firstInput, '3');
const secondRow = rows[2];
const secondInput = within(secondRow).getByRole('spinbutton');
await user.clear(secondInput);
await user.type(secondInput, '1');
await user.click(screen.getByRole('button', { name: /批量更新|儲存/i }));
getByRole → 語意化(button, textbox, combobox, spinbutton, heading)
getByLabelText → 表單欄位有 label
getByPlaceholderText → 有 placeholder
getByText → 按鈕文字、連結文字
getByTestId → 最後手段