用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/dev-hann/song --skill hook-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Runs PM-Designer-Doc triple review protocol to reach consensus on feature specs before implementation. Includes domain documentation consistency check. Use before implementing any new screen, component, or significant feature.
Create Server and Client React components with proper props, state, and patterns
Enforce docs ↔ code consistency before and after implementation. Launches doc-reviewer agent to cross-check domain documentation against actual codebase, detects drift, and ensures documentation updates accompany code changes.
基于 SOC 职业分类
正在显示 SKILL.md
| name | hook-testing |
| description | Test React Hooks with proper state management and async handling using renderHook |
| license | MIT |
| compatibility | opencode |
| metadata | {"category":"testing","complexity":"intermediate"} |
renderHook from @testing-library/reactwaitForUse this when you need to:
I'll ask clarifying questions if:
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { useYouTubeSearch } from '@/hooks/use-youtube';
vi.mock('@/lib/api-helpers', () => ({
fetchAPI: vi.fn(),
}));
describe('useYouTubeSearch', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should initialize with empty state', () => {
const { result } = renderHook(() => useYouTubeSearch());
expect(result.current.results).toBeNull();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe('');
});
});
describe('Async Operations', () => {
it('should fetch and display search results', async () => {
const mockData = {
results: [
{
id: 'video123',
type: 'video',
title: 'Test Video',
thumbnail: 'https://example.com/thumb.jpg',
channel: { name: 'Test Channel' },
},
],
};
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI).mockResolvedValue(mockData);
const { result } = renderHook(() => useYouTubeSearch());
await result.current.searchVideos('test query');
await waitFor(() => {
expect(result.current.results).toHaveLength(1);
expect(result.current.loading).toBe(false);
});
});
it('should handle search errors', () => {
{ fetchAPI } = ();
vi.(fetchAPI).( ());
{ result } = ( ());
result..();
( {
(result..).();
(result..).();
});
});
});
describe('Loading States', () => {
it('should set loading to true when searching', async () => {
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useYouTubeSearch());
result.current.searchVideos('test query');
expect(result.current.loading).toBe(true);
});
});
describe('State Updates', () => {
it('should update results on multiple searches', async () => {
const mockData1 = {
results: [{ id: 'video1', type: 'video', title: 'Video 1', thumbnail: 'https://example.com/thumb.jpg', channel: { name: 'Test Channel' } }],
};
const mockData2 = {
results: [{ id: 'video2', type: 'video', title: 'Video 2', thumbnail: 'https://example.com/thumb.jpg', channel: { name: 'Test Channel' } }],
};
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI)
.mockResolvedValueOnce(mockData1)
.mockResolvedValueOnce(mockData2);
const { result } = renderHook(() => useYouTubeSearch());
await result.current.searchVideos('query1');
await waitFor(() => {
(result..?.[]?.).();
});
result..();
( {
(result..?.[]?.).();
});
});
});
describe('Edge Cases', () => {
it('should not search with empty query', async () => {
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI).mockResolvedValue({ results: [] });
const { result } = renderHook(() => useYouTubeSearch());
await result.current.searchVideos('');
expect(fetchAPI).not.toHaveBeenCalled();
});
it('should handle null results', async () => {
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI).mockResolvedValue({ results: null });
const { result } = renderHook(() => useYouTubeSearch());
await result.current.searchVideos('test');
await waitFor(() => {
expect(result.current.).();
});
});
});
describe('useYouTubeSearch', () => {
describe('Initialization', () => {
it('should initialize with empty state');
});
describe('Loading States', () => {
it('should set loading to true when searching');
});
describe('Success Cases', () => {
it('should fetch and display search results');
it('should update results on multiple searches');
});
describe('Error Cases', () => {
it('should handle search errors');
it('should handle network errors');
});
describe('Edge Cases', () => {
it('should not search with empty query');
it('should handle null results');
});
});
describe('AAA Pattern Example', () => {
it('should fetch and display search results', async () => {
// Arrange
const mockData = { results: [{ id: 'video123', type: 'video', title: 'Test Video', thumbnail: 'https://example.com/thumb.jpg', channel: { name: 'Test Channel' } }] };
const { fetchAPI } = await import('@/lib/api-helpers');
vi.mocked(fetchAPI).mockResolvedValue(mockData);
// Act
const { result } = renderHook(() => useYouTubeSearch());
await result.current.searchVideos('test query');
// Assert
await waitFor(() => {
expect(result.current.results).toHaveLength(1);
expect(result.current.loading).toBe(false);
});
});
});
| Test Type | What to Test |
|---|---|
| Hook Initialization | Initial state values |
| Loading States | Loading flag during async operations |
| Success Cases | Correct data after successful operations |
| Error Cases | Error messages and state on failures |
| State Updates | State changes on subsequent calls |
| Edge Cases | Empty inputs, null values, etc. |
| Pattern | Description |
|---|---|
renderHook | Render a hook for testing |
waitFor | Wait for async state updates |
vi.clearAllMocks | Clear all mocks before each test |
vi.mocked | Type-safe mock access |
await import() | Lazy import for mocking |
Related SKILLS: component-testing.md, api-testing.md, utility-testing.md