@deepstorm/cli 0.11.0 → 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 +839 -585
- 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/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 +129 -100
- package/dist/skills/sweep-explore/references/explore-flow-template.md +9 -9
- 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 +38 -15
- 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 +4 -3
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
<!-- UserAvatar.vue -->
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
interface Props {
|
|
9
|
-
username: string
|
|
10
|
-
avatar?: string
|
|
11
|
-
size?: 'sm' | 'md' | 'lg'
|
|
12
|
-
onClick?: () => void
|
|
9
|
+
username: string;
|
|
10
|
+
avatar?: string;
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
onClick?: () => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const props = withDefaults(defineProps<Props>(), {
|
|
16
16
|
size: 'md',
|
|
17
|
-
})
|
|
17
|
+
});
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<template>
|
|
@@ -30,12 +30,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
32
|
// UserAvatar.test.ts
|
|
33
|
-
import { mount } from '@vue/test-utils'
|
|
34
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
35
|
-
import UserAvatar from './UserAvatar.vue'
|
|
33
|
+
import { mount } from '@vue/test-utils';
|
|
34
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
35
|
+
import UserAvatar from './UserAvatar.vue';
|
|
36
36
|
|
|
37
37
|
describe('UserAvatar', () => {
|
|
38
|
-
const mockUser = { username: 'alice', avatar: 'https://example.com/avatar.png' }
|
|
38
|
+
const mockUser = { username: 'alice', avatar: 'https://example.com/avatar.png' };
|
|
39
39
|
|
|
40
40
|
it('renders with avatar image', () => {
|
|
41
41
|
const wrapper = mount(UserAvatar, {
|
|
@@ -43,12 +43,12 @@ describe('UserAvatar', () => {
|
|
|
43
43
|
username: mockUser.username,
|
|
44
44
|
avatar: mockUser.avatar,
|
|
45
45
|
},
|
|
46
|
-
})
|
|
46
|
+
});
|
|
47
47
|
|
|
48
|
-
const img = wrapper.find('img')
|
|
49
|
-
expect(img.attributes('alt')).toBe('alice')
|
|
50
|
-
expect(img.attributes('src')).toBe(mockUser.avatar)
|
|
51
|
-
})
|
|
48
|
+
const img = wrapper.find('img');
|
|
49
|
+
expect(img.attributes('alt')).toBe('alice');
|
|
50
|
+
expect(img.attributes('src')).toBe(mockUser.avatar);
|
|
51
|
+
});
|
|
52
52
|
|
|
53
53
|
it('renders with size classes', () => {
|
|
54
54
|
const wrapper = mount(UserAvatar, {
|
|
@@ -56,25 +56,25 @@ describe('UserAvatar', () => {
|
|
|
56
56
|
username: mockUser.username,
|
|
57
57
|
size: 'lg',
|
|
58
58
|
},
|
|
59
|
-
})
|
|
59
|
+
});
|
|
60
60
|
|
|
61
61
|
// Ant Design Vue Avatar 通过 size 属性控制尺寸
|
|
62
|
-
expect(wrapper.findComponent({ name: 'AAvatar' }).exists()).toBe(true)
|
|
63
|
-
})
|
|
62
|
+
expect(wrapper.findComponent({ name: 'AAvatar' }).exists()).toBe(true);
|
|
63
|
+
});
|
|
64
64
|
|
|
65
65
|
it('is clickable when onClick provided', async () => {
|
|
66
|
-
const handleClick = vi.fn()
|
|
66
|
+
const handleClick = vi.fn();
|
|
67
67
|
const wrapper = mount(UserAvatar, {
|
|
68
68
|
props: {
|
|
69
69
|
username: mockUser.username,
|
|
70
70
|
onClick: handleClick,
|
|
71
71
|
},
|
|
72
|
-
})
|
|
72
|
+
});
|
|
73
73
|
|
|
74
|
-
await wrapper.findComponent({ name: 'AAvatar' }).trigger('click')
|
|
75
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
74
|
+
await wrapper.findComponent({ name: 'AAvatar' }).trigger('click');
|
|
75
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
## 表单测试
|
|
@@ -82,27 +82,27 @@ describe('UserAvatar', () => {
|
|
|
82
82
|
```vue
|
|
83
83
|
<!-- CreateUserForm.vue -->
|
|
84
84
|
<script setup lang="ts">
|
|
85
|
-
import { reactive, ref } from 'vue'
|
|
86
|
-
import { message } from 'ant-design-vue'
|
|
85
|
+
import { reactive, ref } from 'vue';
|
|
86
|
+
import { message } from 'ant-design-vue';
|
|
87
87
|
|
|
88
88
|
interface FormState {
|
|
89
|
-
username: string
|
|
90
|
-
email: string
|
|
91
|
-
role: string | undefined
|
|
89
|
+
username: string;
|
|
90
|
+
email: string;
|
|
91
|
+
role: string | undefined;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const emit = defineEmits<{
|
|
95
|
-
success: []
|
|
96
|
-
}>()
|
|
95
|
+
success: [];
|
|
96
|
+
}>();
|
|
97
97
|
|
|
98
98
|
const formState = reactive<FormState>({
|
|
99
99
|
username: '',
|
|
100
100
|
email: '',
|
|
101
101
|
role: undefined,
|
|
102
|
-
})
|
|
102
|
+
});
|
|
103
103
|
|
|
104
|
-
const loading = ref(false)
|
|
105
|
-
const formRef = ref()
|
|
104
|
+
const loading = ref(false);
|
|
105
|
+
const formRef = ref();
|
|
106
106
|
|
|
107
107
|
const rules = {
|
|
108
108
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
|
@@ -111,26 +111,26 @@ const rules = {
|
|
|
111
111
|
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
|
|
112
112
|
],
|
|
113
113
|
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
|
114
|
-
}
|
|
114
|
+
};
|
|
115
115
|
|
|
116
116
|
async function handleSubmit() {
|
|
117
117
|
try {
|
|
118
|
-
await formRef.value?.validate()
|
|
119
|
-
loading.value = true
|
|
118
|
+
await formRef.value?.validate();
|
|
119
|
+
loading.value = true;
|
|
120
120
|
|
|
121
121
|
const res = await fetch('/api/v1/users', {
|
|
122
122
|
method: 'POST',
|
|
123
123
|
headers: { 'Content-Type': 'application/json' },
|
|
124
124
|
body: JSON.stringify(formState),
|
|
125
|
-
})
|
|
125
|
+
});
|
|
126
126
|
|
|
127
|
-
if (!res.ok) throw new Error('创建失败')
|
|
128
|
-
message.success('用户创建成功')
|
|
129
|
-
emit('success')
|
|
127
|
+
if (!res.ok) throw new Error('创建失败');
|
|
128
|
+
message.success('用户创建成功');
|
|
129
|
+
emit('success');
|
|
130
130
|
} catch (e) {
|
|
131
|
-
if (e instanceof Error) message.error(e.message)
|
|
131
|
+
if (e instanceof Error) message.error(e.message);
|
|
132
132
|
} finally {
|
|
133
|
-
loading.value = false
|
|
133
|
+
loading.value = false;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
</script>
|
|
@@ -159,9 +159,7 @@ async function handleSubmit() {
|
|
|
159
159
|
</a-form-item>
|
|
160
160
|
|
|
161
161
|
<a-form-item :wrapper-col="{ offset: 6, span: 16 }">
|
|
162
|
-
<a-button type="primary" html-type="submit" :loading="loading">
|
|
163
|
-
创建
|
|
164
|
-
</a-button>
|
|
162
|
+
<a-button type="primary" html-type="submit" :loading="loading"> 创建 </a-button>
|
|
165
163
|
</a-form-item>
|
|
166
164
|
</a-form>
|
|
167
165
|
</template>
|
|
@@ -169,183 +167,184 @@ async function handleSubmit() {
|
|
|
169
167
|
|
|
170
168
|
```typescript
|
|
171
169
|
// CreateUserForm.test.ts
|
|
172
|
-
import { mount } from '@vue/test-utils'
|
|
173
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
174
|
-
import { nextTick } from 'vue'
|
|
175
|
-
import CreateUserForm from './CreateUserForm.vue'
|
|
170
|
+
import { mount } from '@vue/test-utils';
|
|
171
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
172
|
+
import { nextTick } from 'vue';
|
|
173
|
+
import CreateUserForm from './CreateUserForm.vue';
|
|
176
174
|
|
|
177
175
|
// Mock Ant Design Vue message
|
|
178
176
|
vi.mock('ant-design-vue', async () => {
|
|
179
|
-
const actual = await vi.importActual('ant-design-vue')
|
|
180
|
-
return { ...actual, message: { success: vi.fn(), error: vi.fn() } }
|
|
181
|
-
})
|
|
177
|
+
const actual = await vi.importActual('ant-design-vue');
|
|
178
|
+
return { ...actual, message: { success: vi.fn(), error: vi.fn() } };
|
|
179
|
+
});
|
|
182
180
|
|
|
183
181
|
describe('CreateUserForm', () => {
|
|
184
182
|
beforeEach(() => {
|
|
185
|
-
vi.clearAllMocks()
|
|
186
|
-
})
|
|
183
|
+
vi.clearAllMocks();
|
|
184
|
+
});
|
|
187
185
|
|
|
188
186
|
it('renders all form fields', () => {
|
|
189
|
-
const wrapper = mount(CreateUserForm)
|
|
187
|
+
const wrapper = mount(CreateUserForm);
|
|
190
188
|
|
|
191
|
-
expect(wrapper.text()).toContain('用户名')
|
|
192
|
-
expect(wrapper.text()).toContain('邮箱')
|
|
193
|
-
expect(wrapper.text()).toContain('角色')
|
|
194
|
-
})
|
|
189
|
+
expect(wrapper.text()).toContain('用户名');
|
|
190
|
+
expect(wrapper.text()).toContain('邮箱');
|
|
191
|
+
expect(wrapper.text()).toContain('角色');
|
|
192
|
+
});
|
|
195
193
|
|
|
196
194
|
it('shows validation errors on empty submit', async () => {
|
|
197
|
-
const wrapper = mount(CreateUserForm)
|
|
195
|
+
const wrapper = mount(CreateUserForm);
|
|
198
196
|
|
|
199
|
-
await wrapper.find('form').trigger('submit')
|
|
200
|
-
await nextTick()
|
|
197
|
+
await wrapper.find('form').trigger('submit');
|
|
198
|
+
await nextTick();
|
|
201
199
|
|
|
202
|
-
expect(wrapper.text()).toContain('请输入用户名')
|
|
203
|
-
expect(wrapper.text()).toContain('请输入邮箱')
|
|
204
|
-
})
|
|
200
|
+
expect(wrapper.text()).toContain('请输入用户名');
|
|
201
|
+
expect(wrapper.text()).toContain('请输入邮箱');
|
|
202
|
+
});
|
|
205
203
|
|
|
206
204
|
it('submits form with valid data', async () => {
|
|
207
205
|
global.fetch = vi.fn().mockResolvedValue({
|
|
208
206
|
ok: true,
|
|
209
207
|
json: () => Promise.resolve({ id: '1' }),
|
|
210
|
-
})
|
|
208
|
+
});
|
|
211
209
|
|
|
212
|
-
const wrapper = mount(CreateUserForm)
|
|
210
|
+
const wrapper = mount(CreateUserForm);
|
|
213
211
|
|
|
214
212
|
// 填充表单
|
|
215
|
-
await wrapper.find('input').setValue('newuser')
|
|
216
|
-
const inputs = wrapper.findAll('input')
|
|
217
|
-
await inputs[1].setValue('new@example.com')
|
|
213
|
+
await wrapper.find('input').setValue('newuser');
|
|
214
|
+
const inputs = wrapper.findAll('input');
|
|
215
|
+
await inputs[1].setValue('new@example.com');
|
|
218
216
|
|
|
219
217
|
// 提交
|
|
220
|
-
await wrapper.find('form').trigger('submit')
|
|
221
|
-
await nextTick()
|
|
218
|
+
await wrapper.find('form').trigger('submit');
|
|
219
|
+
await nextTick();
|
|
222
220
|
|
|
223
|
-
expect(global.fetch).toHaveBeenCalled()
|
|
224
|
-
})
|
|
225
|
-
})
|
|
221
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
226
224
|
```
|
|
227
225
|
|
|
228
226
|
## 组合式函数测试
|
|
229
227
|
|
|
230
228
|
```typescript
|
|
231
229
|
// useUsers.test.ts
|
|
232
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
233
|
-
import { withSetup } from '@/test/utils'
|
|
230
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
231
|
+
import { withSetup } from '@/test/utils';
|
|
234
232
|
|
|
235
233
|
// withSetup 辅助函数:在测试环境中执行组合式函数
|
|
236
234
|
function withSetup<T>(hook: () => T): T {
|
|
237
|
-
let result: T
|
|
235
|
+
let result: T;
|
|
238
236
|
const wrapper = defineComponent({
|
|
239
237
|
setup() {
|
|
240
|
-
result = hook()
|
|
241
|
-
return () => null
|
|
238
|
+
result = hook();
|
|
239
|
+
return () => null;
|
|
242
240
|
},
|
|
243
|
-
})
|
|
244
|
-
mount(wrapper)
|
|
245
|
-
return result
|
|
241
|
+
});
|
|
242
|
+
mount(wrapper);
|
|
243
|
+
return result!;
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
describe('useUsers', () => {
|
|
249
|
-
const mockUsers = [
|
|
250
|
-
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
251
|
-
]
|
|
247
|
+
const mockUsers = [{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' }];
|
|
252
248
|
|
|
253
249
|
beforeEach(() => {
|
|
254
|
-
vi.clearAllMocks()
|
|
255
|
-
})
|
|
250
|
+
vi.clearAllMocks();
|
|
251
|
+
});
|
|
256
252
|
|
|
257
253
|
it('returns loading state initially', () => {
|
|
258
|
-
global.fetch = vi.fn().mockImplementation(() => new Promise(() => {}))
|
|
254
|
+
global.fetch = vi.fn().mockImplementation(() => new Promise(() => {}));
|
|
259
255
|
|
|
260
|
-
const { loading, users, error } = useUsers()
|
|
256
|
+
const { loading, users, error } = useUsers();
|
|
261
257
|
|
|
262
|
-
expect(loading.value).toBe(true)
|
|
263
|
-
expect(users.value).toEqual([])
|
|
264
|
-
expect(error.value).toBeNull()
|
|
265
|
-
})
|
|
258
|
+
expect(loading.value).toBe(true);
|
|
259
|
+
expect(users.value).toEqual([]);
|
|
260
|
+
expect(error.value).toBeNull();
|
|
261
|
+
});
|
|
266
262
|
|
|
267
263
|
it('returns users on successful fetch', async () => {
|
|
268
264
|
global.fetch = vi.fn().mockResolvedValue({
|
|
269
265
|
ok: true,
|
|
270
266
|
json: () => Promise.resolve({ content: mockUsers, totalElements: 1 }),
|
|
271
|
-
})
|
|
267
|
+
});
|
|
272
268
|
|
|
273
269
|
// 在 setup 中调用组合式函数
|
|
274
|
-
let result: ReturnType<typeof useUsers
|
|
270
|
+
let result: ReturnType<typeof useUsers>;
|
|
275
271
|
mount(
|
|
276
272
|
defineComponent({
|
|
277
273
|
setup() {
|
|
278
|
-
result = useUsers()
|
|
279
|
-
return () => null
|
|
274
|
+
result = useUsers();
|
|
275
|
+
return () => null;
|
|
280
276
|
},
|
|
281
|
-
})
|
|
282
|
-
)
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
283
279
|
|
|
284
280
|
await vi.waitFor(() => {
|
|
285
|
-
expect(result!.loading.value).toBe(false)
|
|
286
|
-
})
|
|
281
|
+
expect(result!.loading.value).toBe(false);
|
|
282
|
+
});
|
|
287
283
|
|
|
288
|
-
expect(result!.users.value).toEqual(mockUsers)
|
|
289
|
-
expect(result!.error.value).toBeNull()
|
|
290
|
-
})
|
|
284
|
+
expect(result!.users.value).toEqual(mockUsers);
|
|
285
|
+
expect(result!.error.value).toBeNull();
|
|
286
|
+
});
|
|
291
287
|
|
|
292
288
|
it('returns error on fetch failure', async () => {
|
|
293
|
-
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'))
|
|
289
|
+
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
294
290
|
|
|
295
|
-
let result: ReturnType<typeof useUsers
|
|
291
|
+
let result: ReturnType<typeof useUsers>;
|
|
296
292
|
mount(
|
|
297
293
|
defineComponent({
|
|
298
294
|
setup() {
|
|
299
|
-
result = useUsers()
|
|
300
|
-
return () => null
|
|
295
|
+
result = useUsers();
|
|
296
|
+
return () => null;
|
|
301
297
|
},
|
|
302
|
-
})
|
|
303
|
-
)
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
304
300
|
|
|
305
301
|
await vi.waitFor(() => {
|
|
306
|
-
expect(result!.loading.value).toBe(false)
|
|
307
|
-
})
|
|
302
|
+
expect(result!.loading.value).toBe(false);
|
|
303
|
+
});
|
|
308
304
|
|
|
309
|
-
expect(result!.error.value).toBeInstanceOf(Error)
|
|
310
|
-
expect(result!.error.value?.message).toBe('Network error')
|
|
311
|
-
})
|
|
305
|
+
expect(result!.error.value).toBeInstanceOf(Error);
|
|
306
|
+
expect(result!.error.value?.message).toBe('Network error');
|
|
307
|
+
});
|
|
312
308
|
|
|
313
309
|
it('refresh resets loading state', async () => {
|
|
314
|
-
let resolvePromise!: (value: unknown) => void
|
|
310
|
+
let resolvePromise!: (value: unknown) => void;
|
|
315
311
|
global.fetch = vi.fn().mockImplementation(
|
|
316
|
-
() =>
|
|
317
|
-
|
|
312
|
+
() =>
|
|
313
|
+
new Promise((resolve) => {
|
|
314
|
+
resolvePromise = resolve;
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
318
317
|
|
|
319
|
-
let result: ReturnType<typeof useUsers
|
|
318
|
+
let result: ReturnType<typeof useUsers>;
|
|
320
319
|
mount(
|
|
321
320
|
defineComponent({
|
|
322
321
|
setup() {
|
|
323
|
-
result = useUsers()
|
|
324
|
-
return () => null
|
|
322
|
+
result = useUsers();
|
|
323
|
+
return () => null;
|
|
325
324
|
},
|
|
326
|
-
})
|
|
327
|
-
)
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
328
327
|
|
|
329
|
-
result!.refresh()
|
|
330
|
-
expect(result!.loading.value).toBe(true)
|
|
331
|
-
})
|
|
332
|
-
})
|
|
328
|
+
result!.refresh();
|
|
329
|
+
expect(result!.loading.value).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
333
332
|
```
|
|
334
333
|
|
|
335
334
|
## Ant Design Vue 组件集成测试
|
|
336
335
|
|
|
337
336
|
```typescript
|
|
338
337
|
// UserTable.test.ts
|
|
339
|
-
import { mount } from '@vue/test-utils'
|
|
340
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
341
|
-
import { defineComponent } from 'vue'
|
|
342
|
-
import { Table } from 'ant-design-vue'
|
|
338
|
+
import { mount } from '@vue/test-utils';
|
|
339
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
340
|
+
import { defineComponent } from 'vue';
|
|
341
|
+
import { Table } from 'ant-design-vue';
|
|
343
342
|
|
|
344
343
|
interface User {
|
|
345
|
-
id: string
|
|
346
|
-
username: string
|
|
347
|
-
email: string
|
|
348
|
-
role: string
|
|
344
|
+
id: string;
|
|
345
|
+
username: string;
|
|
346
|
+
email: string;
|
|
347
|
+
role: string;
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
describe('UserTable with Ant Design Vue', () => {
|
|
@@ -353,12 +352,12 @@ describe('UserTable with Ant Design Vue', () => {
|
|
|
353
352
|
{ title: '用户名', dataIndex: 'username', key: 'username' },
|
|
354
353
|
{ title: '邮箱', dataIndex: 'email', key: 'email' },
|
|
355
354
|
{ title: '角色', dataIndex: 'role', key: 'role' },
|
|
356
|
-
]
|
|
355
|
+
];
|
|
357
356
|
|
|
358
357
|
const mockData: User[] = [
|
|
359
358
|
{ id: '1', username: 'alice', email: 'alice@example.com', role: 'ADMIN' },
|
|
360
359
|
{ id: '2', username: 'bob', email: 'bob@example.com', role: 'EDITOR' },
|
|
361
|
-
]
|
|
360
|
+
];
|
|
362
361
|
|
|
363
362
|
it('renders table with data', () => {
|
|
364
363
|
const wrapper = mount(
|
|
@@ -372,12 +371,12 @@ describe('UserTable with Ant Design Vue', () => {
|
|
|
372
371
|
`,
|
|
373
372
|
components: { ATable: Table },
|
|
374
373
|
setup: () => ({ data: mockData, columns }),
|
|
375
|
-
})
|
|
376
|
-
)
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
377
376
|
|
|
378
|
-
expect(wrapper.text()).toContain('alice')
|
|
379
|
-
expect(wrapper.text()).toContain('bob')
|
|
380
|
-
})
|
|
377
|
+
expect(wrapper.text()).toContain('alice');
|
|
378
|
+
expect(wrapper.text()).toContain('bob');
|
|
379
|
+
});
|
|
381
380
|
|
|
382
381
|
it('shows empty state when no data', () => {
|
|
383
382
|
const wrapper = mount(
|
|
@@ -392,12 +391,12 @@ describe('UserTable with Ant Design Vue', () => {
|
|
|
392
391
|
`,
|
|
393
392
|
components: { ATable: Table },
|
|
394
393
|
setup: () => ({ columns }),
|
|
395
|
-
})
|
|
396
|
-
)
|
|
394
|
+
}),
|
|
395
|
+
);
|
|
397
396
|
|
|
398
|
-
expect(wrapper.text()).toContain('暂无数据')
|
|
399
|
-
})
|
|
400
|
-
})
|
|
397
|
+
expect(wrapper.text()).toContain('暂无数据');
|
|
398
|
+
});
|
|
399
|
+
});
|
|
401
400
|
```
|
|
402
401
|
|
|
403
402
|
## 路由组件测试
|
|
@@ -405,14 +404,14 @@ describe('UserTable with Ant Design Vue', () => {
|
|
|
405
404
|
```vue
|
|
406
405
|
<!-- ProtectedRoute.vue -->
|
|
407
406
|
<script setup lang="ts">
|
|
408
|
-
import { useAuthStore } from '@/stores/auth'
|
|
409
|
-
import { useRouter } from 'vue-router'
|
|
407
|
+
import { useAuthStore } from '@/stores/auth';
|
|
408
|
+
import { useRouter } from 'vue-router';
|
|
410
409
|
|
|
411
|
-
const auth = useAuthStore()
|
|
412
|
-
const router = useRouter()
|
|
410
|
+
const auth = useAuthStore();
|
|
411
|
+
const router = useRouter();
|
|
413
412
|
|
|
414
413
|
if (!auth.isAuthenticated) {
|
|
415
|
-
router.replace({ name: 'login' })
|
|
414
|
+
router.replace({ name: 'login' });
|
|
416
415
|
}
|
|
417
416
|
</script>
|
|
418
417
|
|
|
@@ -423,11 +422,11 @@ if (!auth.isAuthenticated) {
|
|
|
423
422
|
|
|
424
423
|
```typescript
|
|
425
424
|
// ProtectedRoute.test.ts
|
|
426
|
-
import { mount } from '@vue/test-utils'
|
|
427
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
428
|
-
import { createRouter, createWebHistory } from 'vue-router'
|
|
429
|
-
import { createTestingPinia } from '@pinia/testing'
|
|
430
|
-
import ProtectedRoute from './ProtectedRoute.vue'
|
|
425
|
+
import { mount } from '@vue/test-utils';
|
|
426
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
427
|
+
import { createRouter, createWebHistory } from 'vue-router';
|
|
428
|
+
import { createTestingPinia } from '@pinia/testing';
|
|
429
|
+
import ProtectedRoute from './ProtectedRoute.vue';
|
|
431
430
|
|
|
432
431
|
describe('ProtectedRoute', () => {
|
|
433
432
|
it('redirects to login when not authenticated', () => {
|
|
@@ -449,9 +448,9 @@ describe('ProtectedRoute', () => {
|
|
|
449
448
|
}),
|
|
450
449
|
],
|
|
451
450
|
},
|
|
452
|
-
})
|
|
451
|
+
});
|
|
453
452
|
|
|
454
|
-
expect(wrapper.text()).not.toContain('Protected Content')
|
|
455
|
-
})
|
|
456
|
-
})
|
|
453
|
+
expect(wrapper.text()).not.toContain('Protected Content');
|
|
454
|
+
});
|
|
455
|
+
});
|
|
457
456
|
```
|