@deepstorm/cli 0.10.2 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/build-registry.js +11 -5
- package/dist/cli.js +1256 -634
- package/dist/mcp/code-hosting/github.json +14 -1
- package/dist/mcp/docs-reference/context7.json +1 -10
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +8 -5
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +10 -8
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +21 -20
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +17 -17
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +20 -20
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +8 -8
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +13 -12
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +10 -10
- package/dist/registry.json +10 -0
- package/dist/skills/atoll-ops/SKILL.md +4 -0
- package/dist/skills/reef-commit/SKILL.md +9 -5
- package/dist/skills/reef-commit/scripts/branch-check.mjs +5 -11
- package/dist/skills/reef-commit/scripts/check-openspec-status.mjs +6 -12
- package/dist/skills/reef-commit/scripts/collect-git-context.mjs +13 -11
- package/dist/skills/reef-gen-backend/variants/java/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/nodejs/steps.md +7 -7
- package/dist/skills/reef-gen-backend/variants/python/steps.md +7 -7
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +5 -5
- package/dist/skills/reef-gen-frontend/variants/react/steps.md +6 -6
- package/dist/skills/reef-gen-frontend/variants/vue/steps.md +6 -6
- package/dist/skills/reef-harden/EXAMPLES.md +12 -10
- package/dist/skills/reef-harden/SKILL.md +6 -0
- package/dist/skills/reef-harden/scripts/find-change-dir.mjs +13 -13
- package/dist/skills/reef-pr/SKILL.md +7 -0
- package/dist/skills/reef-pr/scripts/create-pr.mjs +31 -29
- package/dist/skills/reef-scope/SKILL.md +6 -6
- package/dist/skills/reef-start/references/jira-start-subagent.md +7 -7
- package/dist/skills/reef-start/references/risk-routing-card.md +28 -25
- package/dist/skills/reef-start/references/stage-4-implementation.md +33 -21
- package/dist/skills/reef-start/references/superpowers-gate.md +9 -9
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +11 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/jackson-polymorphism.md +36 -40
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +11 -10
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +24 -21
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +10 -8
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +2 -2
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +5 -5
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +1 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +12 -12
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +13 -12
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +7 -7
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +6 -6
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +8 -8
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +8 -7
- package/dist/skills/reef-style-backend/fragments/nodejs/eslint-config.json +1 -4
- package/dist/skills/reef-style-backend/fragments/nodejs/nestjs-structure.md +9 -9
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +2 -1
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +10 -10
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +24 -22
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +10 -9
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +10 -7
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +2 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +11 -9
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +3 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +3 -4
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +31 -29
- package/dist/skills/reef-style-backend/variants/nodejs/examples/module-example.md +2 -8
- package/dist/skills/reef-style-backend/variants/nodejs/quick-reference.md +35 -36
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +32 -31
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +9 -7
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +9 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +4 -4
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +135 -140
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +32 -32
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +161 -162
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +42 -42
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +16 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +72 -87
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +14 -10
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +93 -111
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +29 -33
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +14 -23
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +15 -17
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +22 -5
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +52 -22
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +17 -20
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +8 -11
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +27 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +14 -27
- package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +45 -52
- package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +44 -44
- package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +56 -85
- package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +114 -88
- package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +48 -42
- package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +24 -43
- package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +48 -49
- package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +45 -45
- package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +64 -68
- package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +85 -84
- package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +46 -51
- package/dist/skills/reef-testcase/SKILL.md +19 -19
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +8 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +16 -16
- package/dist/skills/sweep-explore/SKILL.md +824 -0
- package/dist/skills/sweep-explore/references/explore-flow-template.md +61 -0
- package/dist/skills/sweep-init/SKILL.md +4 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +6 -11
- package/dist/skills/sweep-init/scripts/init-project.mjs +68 -51
- package/dist/skills/sweep-plan/references/test-flow-template.md +12 -6
- package/dist/skills/sweep-record/SKILL.md +358 -0
- package/dist/skills/sweep-run/SKILL.md +59 -50
- package/dist/skills/sweep-run/scripts/env-manager.mjs +4 -5
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +7 -6
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +4 -15
- package/dist/skills/sweep-run/scripts/generate-report.mjs +1 -2
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +107 -14
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +19 -9
- package/dist/skills/tide-discuss/references/checklists.md +32 -32
- package/dist/skills/tide-discuss/references/data-format.md +85 -77
- package/dist/skills/tide-discuss/references/prd-template.md +21 -19
- package/dist/skills/tide-discuss/references/publish-flow.md +30 -11
- package/dist/skills/tide-discuss/references/role-prompts.md +5 -5
- package/package.json +5 -3
|
@@ -4,191 +4,192 @@
|
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
// UserAvatar.test.tsx
|
|
7
|
-
import { render, screen } from '@testing-library/react'
|
|
8
|
-
import { describe, it, expect } from 'vitest'
|
|
9
|
-
import { UserAvatar } from './UserAvatar'
|
|
7
|
+
import { render, screen } from '@testing-library/react';
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { UserAvatar } from './UserAvatar';
|
|
10
10
|
|
|
11
11
|
describe('UserAvatar', () => {
|
|
12
|
-
const mockUser = { username: 'alice', avatar: 'https://example.com/avatar.png' }
|
|
12
|
+
const mockUser = { username: 'alice', avatar: 'https://example.com/avatar.png' };
|
|
13
13
|
|
|
14
14
|
it('renders with avatar image', () => {
|
|
15
|
-
render(<UserAvatar user={mockUser} />)
|
|
16
|
-
const img = screen.getByAltText('alice')
|
|
17
|
-
expect(img).toBeInTheDocument()
|
|
18
|
-
expect(img).toHaveAttribute('src', mockUser.avatar)
|
|
19
|
-
})
|
|
15
|
+
render(<UserAvatar user={mockUser} />);
|
|
16
|
+
const img = screen.getByAltText('alice');
|
|
17
|
+
expect(img).toBeInTheDocument();
|
|
18
|
+
expect(img).toHaveAttribute('src', mockUser.avatar);
|
|
19
|
+
});
|
|
20
20
|
|
|
21
21
|
it('renders with size classes', () => {
|
|
22
|
-
const { container } = render(<UserAvatar user={mockUser} size="lg" />)
|
|
22
|
+
const { container } = render(<UserAvatar user={mockUser} size="lg" />);
|
|
23
23
|
// Ant Design Avatar 通过 size 属性控制尺寸
|
|
24
|
-
expect(container.querySelector('.ant-avatar')).toBeInTheDocument()
|
|
25
|
-
})
|
|
24
|
+
expect(container.querySelector('.ant-avatar')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
26
|
|
|
27
27
|
it('is clickable when onClick provided', async () => {
|
|
28
|
-
const handleClick = vi.fn()
|
|
29
|
-
render(<UserAvatar user={mockUser} onClick={handleClick} />)
|
|
28
|
+
const handleClick = vi.fn();
|
|
29
|
+
render(<UserAvatar user={mockUser} onClick={handleClick} />);
|
|
30
30
|
|
|
31
|
-
await userEvent.click(screen.getByAltText('alice'))
|
|
32
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
31
|
+
await userEvent.click(screen.getByAltText('alice'));
|
|
32
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## 表单测试
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
40
|
// CreateUserForm.test.tsx
|
|
41
|
-
import { render, screen, waitFor } from '@testing-library/react'
|
|
42
|
-
import userEvent from '@testing-library/user-event'
|
|
43
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
44
|
-
import { CreateUserForm } from './CreateUserForm'
|
|
41
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
42
|
+
import userEvent from '@testing-library/user-event';
|
|
43
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
44
|
+
import { CreateUserForm } from './CreateUserForm';
|
|
45
45
|
|
|
46
46
|
// Mock message
|
|
47
47
|
vi.mock('antd', async () => {
|
|
48
|
-
const actual = await vi.importActual('antd')
|
|
49
|
-
return { ...actual, message: { success: vi.fn(), error: vi.fn() } }
|
|
50
|
-
})
|
|
48
|
+
const actual = await vi.importActual('antd');
|
|
49
|
+
return { ...actual, message: { success: vi.fn(), error: vi.fn() } };
|
|
50
|
+
});
|
|
51
51
|
|
|
52
52
|
describe('CreateUserForm', () => {
|
|
53
53
|
beforeEach(() => {
|
|
54
|
-
vi.clearAllMocks()
|
|
55
|
-
})
|
|
54
|
+
vi.clearAllMocks();
|
|
55
|
+
});
|
|
56
56
|
|
|
57
57
|
it('renders all form fields', () => {
|
|
58
|
-
render(<CreateUserForm />)
|
|
58
|
+
render(<CreateUserForm />);
|
|
59
59
|
|
|
60
|
-
expect(screen.getByLabelText('用户名')).toBeInTheDocument()
|
|
61
|
-
expect(screen.getByLabelText('邮箱')).toBeInTheDocument()
|
|
62
|
-
expect(screen.getByLabelText('角色')).toBeInTheDocument()
|
|
63
|
-
expect(screen.getByRole('button', { name: '创建' })).toBeInTheDocument()
|
|
64
|
-
})
|
|
60
|
+
expect(screen.getByLabelText('用户名')).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByLabelText('邮箱')).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByLabelText('角色')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByRole('button', { name: '创建' })).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
65
|
|
|
66
66
|
it('shows validation errors on empty submit', async () => {
|
|
67
|
-
render(<CreateUserForm />)
|
|
67
|
+
render(<CreateUserForm />);
|
|
68
68
|
|
|
69
|
-
await userEvent.click(screen.getByRole('button', { name: '创建' }))
|
|
69
|
+
await userEvent.click(screen.getByRole('button', { name: '创建' }));
|
|
70
70
|
|
|
71
71
|
await waitFor(() => {
|
|
72
|
-
expect(screen.getByText('请输入用户名')).toBeInTheDocument()
|
|
73
|
-
expect(screen.getByText('请输入邮箱')).toBeInTheDocument()
|
|
74
|
-
})
|
|
75
|
-
})
|
|
72
|
+
expect(screen.getByText('请输入用户名')).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText('请输入邮箱')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
76
|
|
|
77
77
|
it('submits form with valid data', async () => {
|
|
78
|
-
const createUser = vi.fn().mockResolvedValue({ id: '1' })
|
|
79
|
-
render(<CreateUserForm onSubmit={createUser} />)
|
|
78
|
+
const createUser = vi.fn().mockResolvedValue({ id: '1' });
|
|
79
|
+
render(<CreateUserForm onSubmit={createUser} />);
|
|
80
80
|
|
|
81
|
-
await userEvent.type(screen.getByLabelText('用户名'), 'newuser')
|
|
82
|
-
await userEvent.type(screen.getByLabelText('邮箱'), 'new@example.com')
|
|
81
|
+
await userEvent.type(screen.getByLabelText('用户名'), 'newuser');
|
|
82
|
+
await userEvent.type(screen.getByLabelText('邮箱'), 'new@example.com');
|
|
83
83
|
|
|
84
84
|
// Ant Design Select 需要特殊处理
|
|
85
|
-
const roleSelect = screen.getByLabelText('角色')
|
|
86
|
-
await userEvent.click(roleSelect)
|
|
87
|
-
await userEvent.click(screen.getByText('编辑'))
|
|
85
|
+
const roleSelect = screen.getByLabelText('角色');
|
|
86
|
+
await userEvent.click(roleSelect);
|
|
87
|
+
await userEvent.click(screen.getByText('编辑'));
|
|
88
88
|
|
|
89
|
-
await userEvent.click(screen.getByRole('button', { name: '创建' }))
|
|
89
|
+
await userEvent.click(screen.getByRole('button', { name: '创建' }));
|
|
90
90
|
|
|
91
91
|
await waitFor(() => {
|
|
92
92
|
expect(createUser).toHaveBeenCalledWith({
|
|
93
93
|
username: 'newuser',
|
|
94
94
|
email: 'new@example.com',
|
|
95
95
|
role: 'EDITOR',
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
})
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Hook 测试
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
105
|
// useUsers.test.ts
|
|
106
|
-
import { renderHook, act, waitFor } from '@testing-library/react'
|
|
107
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
108
|
-
import { useUsers } from './useUsers'
|
|
106
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
107
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
108
|
+
import { useUsers } from './useUsers';
|
|
109
109
|
|
|
110
110
|
describe('useUsers', () => {
|
|
111
|
-
const mockUsers = [
|
|
112
|
-
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
113
|
-
]
|
|
111
|
+
const mockUsers = [{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' }];
|
|
114
112
|
|
|
115
113
|
beforeEach(() => {
|
|
116
|
-
vi.clearAllMocks()
|
|
117
|
-
})
|
|
114
|
+
vi.clearAllMocks();
|
|
115
|
+
});
|
|
118
116
|
|
|
119
117
|
it('returns loading state initially', () => {
|
|
120
|
-
global.fetch = vi.fn().mockImplementation(() => new Promise(() => {})) // 永不 resolve
|
|
118
|
+
global.fetch = vi.fn().mockImplementation(() => new Promise(() => {})); // 永不 resolve
|
|
121
119
|
|
|
122
|
-
const { result } = renderHook(() => useUsers())
|
|
120
|
+
const { result } = renderHook(() => useUsers());
|
|
123
121
|
|
|
124
|
-
expect(result.current.loading).toBe(true)
|
|
125
|
-
expect(result.current.users).toEqual([])
|
|
126
|
-
expect(result.current.error).toBeNull()
|
|
127
|
-
})
|
|
122
|
+
expect(result.current.loading).toBe(true);
|
|
123
|
+
expect(result.current.users).toEqual([]);
|
|
124
|
+
expect(result.current.error).toBeNull();
|
|
125
|
+
});
|
|
128
126
|
|
|
129
127
|
it('returns users on successful fetch', async () => {
|
|
130
128
|
global.fetch = vi.fn().mockResolvedValue({
|
|
131
129
|
ok: true,
|
|
132
130
|
json: () => Promise.resolve({ content: mockUsers, totalElements: 1 }),
|
|
133
|
-
})
|
|
131
|
+
});
|
|
134
132
|
|
|
135
|
-
const { result } = renderHook(() => useUsers())
|
|
133
|
+
const { result } = renderHook(() => useUsers());
|
|
136
134
|
|
|
137
135
|
await waitFor(() => {
|
|
138
|
-
expect(result.current.loading).toBe(false)
|
|
139
|
-
})
|
|
136
|
+
expect(result.current.loading).toBe(false);
|
|
137
|
+
});
|
|
140
138
|
|
|
141
|
-
expect(result.current.users).toEqual(mockUsers)
|
|
142
|
-
expect(result.current.error).toBeNull()
|
|
143
|
-
})
|
|
139
|
+
expect(result.current.users).toEqual(mockUsers);
|
|
140
|
+
expect(result.current.error).toBeNull();
|
|
141
|
+
});
|
|
144
142
|
|
|
145
143
|
it('returns error on fetch failure', async () => {
|
|
146
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'))
|
|
144
|
+
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
147
145
|
|
|
148
|
-
const { result } = renderHook(() => useUsers())
|
|
146
|
+
const { result } = renderHook(() => useUsers());
|
|
149
147
|
|
|
150
148
|
await waitFor(() => {
|
|
151
|
-
expect(result.current.loading).toBe(false)
|
|
152
|
-
})
|
|
149
|
+
expect(result.current.loading).toBe(false);
|
|
150
|
+
});
|
|
153
151
|
|
|
154
|
-
expect(result.current.error).toBeInstanceOf(Error)
|
|
155
|
-
expect(result.current.error?.message).toBe('Network error')
|
|
156
|
-
expect(result.current.users).toEqual([])
|
|
157
|
-
})
|
|
152
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
153
|
+
expect(result.current.error?.message).toBe('Network error');
|
|
154
|
+
expect(result.current.users).toEqual([]);
|
|
155
|
+
});
|
|
158
156
|
|
|
159
157
|
it('refresh resets loading state', async () => {
|
|
160
|
-
let resolvePromise!: (value: unknown) => void
|
|
158
|
+
let resolvePromise!: (value: unknown) => void;
|
|
161
159
|
global.fetch = vi.fn().mockImplementation(
|
|
162
|
-
() =>
|
|
163
|
-
|
|
160
|
+
() =>
|
|
161
|
+
new Promise((resolve) => {
|
|
162
|
+
resolvePromise = resolve;
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
164
165
|
|
|
165
|
-
const { result } = renderHook(() => useUsers())
|
|
166
|
+
const { result } = renderHook(() => useUsers());
|
|
166
167
|
|
|
167
168
|
act(() => {
|
|
168
|
-
result.current.refresh()
|
|
169
|
-
})
|
|
169
|
+
result.current.refresh();
|
|
170
|
+
});
|
|
170
171
|
|
|
171
172
|
// refresh should trigger a new fetch, loading goes back to true
|
|
172
|
-
expect(result.current.loading).toBe(true)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
173
|
+
expect(result.current.loading).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
175
176
|
```
|
|
176
177
|
|
|
177
178
|
## Ant Design 组件集成测试
|
|
178
179
|
|
|
179
180
|
```tsx
|
|
180
181
|
// UserTable.test.tsx
|
|
181
|
-
import { render, screen, within } from '@testing-library/react'
|
|
182
|
-
import userEvent from '@testing-library/user-event'
|
|
183
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
184
|
-
import { Table } from 'antd'
|
|
185
|
-
import type { ColumnsType } from 'antd/es/table'
|
|
182
|
+
import { render, screen, within } from '@testing-library/react';
|
|
183
|
+
import userEvent from '@testing-library/user-event';
|
|
184
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
185
|
+
import { Table } from 'antd';
|
|
186
|
+
import type { ColumnsType } from 'antd/es/table';
|
|
186
187
|
|
|
187
188
|
interface User {
|
|
188
|
-
id: string
|
|
189
|
-
username: string
|
|
190
|
-
email: string
|
|
191
|
-
role: string
|
|
189
|
+
id: string;
|
|
190
|
+
username: string;
|
|
191
|
+
email: string;
|
|
192
|
+
role: string;
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
describe('UserTable with Ant Design', () => {
|
|
@@ -196,41 +197,28 @@ describe('UserTable with Ant Design', () => {
|
|
|
196
197
|
{ title: '用户名', dataIndex: 'username', key: 'username' },
|
|
197
198
|
{ title: '邮箱', dataIndex: 'email', key: 'email' },
|
|
198
199
|
{ title: '角色', dataIndex: 'role', key: 'role' },
|
|
199
|
-
]
|
|
200
|
+
];
|
|
200
201
|
|
|
201
202
|
const mockData: User[] = [
|
|
202
203
|
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
203
204
|
{ id: '2', username: 'bob', email: 'bob@example.com', role: 'EDITOR' },
|
|
204
|
-
]
|
|
205
|
+
];
|
|
205
206
|
|
|
206
207
|
it('renders table with data', () => {
|
|
207
|
-
render(
|
|
208
|
-
<Table
|
|
209
|
-
columns={columns}
|
|
210
|
-
dataSource={mockData}
|
|
211
|
-
rowKey="id"
|
|
212
|
-
/>
|
|
213
|
-
)
|
|
208
|
+
render(<Table columns={columns} dataSource={mockData} rowKey="id" />);
|
|
214
209
|
|
|
215
|
-
expect(screen.getByText('alice')).toBeInTheDocument()
|
|
216
|
-
expect(screen.getByText('bob')).toBeInTheDocument()
|
|
217
|
-
})
|
|
210
|
+
expect(screen.getByText('alice')).toBeInTheDocument();
|
|
211
|
+
expect(screen.getByText('bob')).toBeInTheDocument();
|
|
212
|
+
});
|
|
218
213
|
|
|
219
214
|
it('shows empty state when no data', () => {
|
|
220
|
-
render(
|
|
221
|
-
<Table
|
|
222
|
-
columns={columns}
|
|
223
|
-
dataSource={[]}
|
|
224
|
-
rowKey="id"
|
|
225
|
-
locale={{ emptyText: '暂无数据' }}
|
|
226
|
-
/>
|
|
227
|
-
)
|
|
215
|
+
render(<Table columns={columns} dataSource={[]} rowKey="id" locale={{ emptyText: '暂无数据' }} />);
|
|
228
216
|
|
|
229
|
-
expect(screen.getByText('暂无数据')).toBeInTheDocument()
|
|
230
|
-
})
|
|
217
|
+
expect(screen.getByText('暂无数据')).toBeInTheDocument();
|
|
218
|
+
});
|
|
231
219
|
|
|
232
220
|
it('handles row click', async () => {
|
|
233
|
-
const handleRow = vi.fn()
|
|
221
|
+
const handleRow = vi.fn();
|
|
234
222
|
render(
|
|
235
223
|
<Table
|
|
236
224
|
columns={columns}
|
|
@@ -239,31 +227,31 @@ describe('UserTable with Ant Design', () => {
|
|
|
239
227
|
onRow={() => ({
|
|
240
228
|
onClick: () => handleRow(),
|
|
241
229
|
})}
|
|
242
|
-
|
|
243
|
-
)
|
|
230
|
+
/>,
|
|
231
|
+
);
|
|
244
232
|
|
|
245
|
-
const rows = screen.getAllByRole('row')
|
|
233
|
+
const rows = screen.getAllByRole('row');
|
|
246
234
|
// 第一行是 header, 第二行是第一个数据行
|
|
247
|
-
await userEvent.click(rows[1])
|
|
248
|
-
expect(handleRow).toHaveBeenCalled()
|
|
249
|
-
})
|
|
250
|
-
})
|
|
235
|
+
await userEvent.click(rows[1]);
|
|
236
|
+
expect(handleRow).toHaveBeenCalled();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
251
239
|
```
|
|
252
240
|
|
|
253
241
|
## 路由组件测试
|
|
254
242
|
|
|
255
243
|
```tsx
|
|
256
244
|
// ProtectedRoute.test.tsx
|
|
257
|
-
import { render, screen } from '@testing-library/react'
|
|
258
|
-
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
|
259
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
245
|
+
import { render, screen } from '@testing-library/react';
|
|
246
|
+
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
|
247
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
260
248
|
|
|
261
249
|
// Mock auth hook
|
|
262
250
|
vi.mock('../hooks/useAuth', () => ({
|
|
263
251
|
useAuth: () => ({ user: null, loading: false }),
|
|
264
|
-
}))
|
|
252
|
+
}));
|
|
265
253
|
|
|
266
|
-
import { ProtectedRoute } from './ProtectedRoute'
|
|
254
|
+
import { ProtectedRoute } from './ProtectedRoute';
|
|
267
255
|
|
|
268
256
|
describe('ProtectedRoute', () => {
|
|
269
257
|
it('redirects to login when not authenticated', () => {
|
|
@@ -271,13 +259,20 @@ describe('ProtectedRoute', () => {
|
|
|
271
259
|
<MemoryRouter initialEntries={['/dashboard']}>
|
|
272
260
|
<Routes>
|
|
273
261
|
<Route path="/login" element={<div>登录页</div>} />
|
|
274
|
-
<Route
|
|
262
|
+
<Route
|
|
263
|
+
path="/dashboard"
|
|
264
|
+
element={
|
|
265
|
+
<ProtectedRoute>
|
|
266
|
+
<div>仪表盘</div>
|
|
267
|
+
</ProtectedRoute>
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
275
270
|
</Routes>
|
|
276
|
-
</MemoryRouter
|
|
277
|
-
)
|
|
271
|
+
</MemoryRouter>,
|
|
272
|
+
);
|
|
278
273
|
|
|
279
|
-
expect(screen.getByText('登录页')).toBeInTheDocument()
|
|
280
|
-
expect(screen.queryByText('仪表盘')).not.toBeInTheDocument()
|
|
281
|
-
})
|
|
282
|
-
})
|
|
274
|
+
expect(screen.getByText('登录页')).toBeInTheDocument();
|
|
275
|
+
expect(screen.queryByText('仪表盘')).not.toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
283
278
|
```
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
// vitest.config.ts
|
|
7
|
-
import { defineConfig } from 'vitest/config'
|
|
8
|
-
import react from '@vitejs/plugin-react'
|
|
7
|
+
import { defineConfig } from 'vitest/config';
|
|
8
|
+
import react from '@vitejs/plugin-react';
|
|
9
9
|
|
|
10
10
|
export default defineConfig({
|
|
11
11
|
plugins: [react()],
|
|
@@ -26,12 +26,12 @@ export default defineConfig({
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
-
})
|
|
29
|
+
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
33
|
// src/test/setup.ts
|
|
34
|
-
import '@testing-library/jest-dom'
|
|
34
|
+
import '@testing-library/jest-dom';
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## 通用原则
|
|
@@ -44,16 +44,16 @@ import '@testing-library/jest-dom'
|
|
|
44
44
|
## 组件测试
|
|
45
45
|
|
|
46
46
|
```tsx
|
|
47
|
-
import { render, screen } from '@testing-library/react'
|
|
48
|
-
import userEvent from '@testing-library/user-event'
|
|
49
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
47
|
+
import { render, screen } from '@testing-library/react';
|
|
48
|
+
import userEvent from '@testing-library/user-event';
|
|
49
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
50
50
|
|
|
51
51
|
// ✅ 正确:通过文本寻找元素
|
|
52
|
-
render(<Button>提交</Button>)
|
|
53
|
-
expect(screen.getByRole('button', { name: '提交' })).toBeInTheDocument()
|
|
52
|
+
render(<Button>提交</Button>);
|
|
53
|
+
expect(screen.getByRole('button', { name: '提交' })).toBeInTheDocument();
|
|
54
54
|
|
|
55
55
|
// ✅ 正确:模拟用户交互
|
|
56
|
-
await userEvent.click(screen.getByRole('button'))
|
|
56
|
+
await userEvent.click(screen.getByRole('button'));
|
|
57
57
|
|
|
58
58
|
// ❌ 避免:通过测试 ID 或实现细节选择
|
|
59
59
|
// screen.getByTestId('submit-btn') // 仅在无其他方式时使用
|
|
@@ -63,20 +63,20 @@ await userEvent.click(screen.getByRole('button'))
|
|
|
63
63
|
## Hook 测试
|
|
64
64
|
|
|
65
65
|
```tsx
|
|
66
|
-
import { renderHook, act } from '@testing-library/react'
|
|
67
|
-
import { describe, it, expect } from 'vitest'
|
|
66
|
+
import { renderHook, act } from '@testing-library/react';
|
|
67
|
+
import { describe, it, expect } from 'vitest';
|
|
68
68
|
|
|
69
69
|
describe('useCounter', () => {
|
|
70
70
|
it('should increment count', () => {
|
|
71
|
-
const { result } = renderHook(() => useCounter())
|
|
71
|
+
const { result } = renderHook(() => useCounter());
|
|
72
72
|
|
|
73
73
|
act(() => {
|
|
74
|
-
result.current.increment()
|
|
75
|
-
})
|
|
74
|
+
result.current.increment();
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
expect(result.current.count).toBe(1)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
77
|
+
expect(result.current.count).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
## 测试文件命名与位置
|
|
@@ -89,22 +89,22 @@ describe('useCounter', () => {
|
|
|
89
89
|
|
|
90
90
|
```tsx
|
|
91
91
|
// 全局 mock API 调用
|
|
92
|
-
import { vi } from 'vitest'
|
|
92
|
+
import { vi } from 'vitest';
|
|
93
93
|
|
|
94
94
|
// Mock fetch
|
|
95
|
-
global.fetch = vi.fn()
|
|
95
|
+
global.fetch = vi.fn();
|
|
96
96
|
|
|
97
97
|
// Mock 模块
|
|
98
98
|
vi.mock('antd', async () => {
|
|
99
|
-
const actual = await vi.importActual('antd')
|
|
99
|
+
const actual = await vi.importActual('antd');
|
|
100
100
|
return {
|
|
101
101
|
...actual,
|
|
102
102
|
message: {
|
|
103
103
|
success: vi.fn(),
|
|
104
104
|
error: vi.fn(),
|
|
105
105
|
},
|
|
106
|
-
}
|
|
107
|
-
})
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
108
|
|
|
109
109
|
// Mock 自定义 Hook
|
|
110
110
|
vi.mock('../hooks/useUsers', () => ({
|
|
@@ -114,7 +114,7 @@ vi.mock('../hooks/useUsers', () => ({
|
|
|
114
114
|
error: null,
|
|
115
115
|
refresh: vi.fn(),
|
|
116
116
|
}),
|
|
117
|
-
}))
|
|
117
|
+
}));
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
## 异步测试
|
|
@@ -125,23 +125,23 @@ it('should load and display users', async () => {
|
|
|
125
125
|
const mockUsers = [
|
|
126
126
|
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
127
127
|
{ id: '2', username: 'bob', email: 'bob@example.com', role: 'EDITOR' },
|
|
128
|
-
]
|
|
128
|
+
];
|
|
129
129
|
|
|
130
130
|
// 模拟 API 响应
|
|
131
131
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
132
132
|
ok: true,
|
|
133
133
|
json: () => Promise.resolve({ content: mockUsers, totalElements: 2 }),
|
|
134
|
-
})
|
|
135
|
-
global.fetch = mockFetch
|
|
134
|
+
});
|
|
135
|
+
global.fetch = mockFetch;
|
|
136
136
|
|
|
137
137
|
// Act
|
|
138
|
-
render(<UserList />)
|
|
138
|
+
render(<UserList />);
|
|
139
139
|
|
|
140
140
|
// Assert — 等待异步渲染完成
|
|
141
|
-
expect(await screen.findByText('alice')).toBeInTheDocument()
|
|
142
|
-
expect(screen.getByText('bob')).toBeInTheDocument()
|
|
143
|
-
expect(screen.getByText('ADMIN')).toBeInTheDocument()
|
|
144
|
-
})
|
|
141
|
+
expect(await screen.findByText('alice')).toBeInTheDocument();
|
|
142
|
+
expect(screen.getByText('bob')).toBeInTheDocument();
|
|
143
|
+
expect(screen.getByText('ADMIN')).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
## 覆盖率要求
|