symfonia-ai-tools 1.0.0
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 +489 -0
- package/bin/cli.mjs +35 -0
- package/lib/installer.mjs +495 -0
- package/lib/questions.mjs +332 -0
- package/lib/ui.mjs +76 -0
- package/lib/utils.mjs +231 -0
- package/package.json +26 -0
- package/templates/base/CLAUDE.md +34 -0
- package/templates/base/_ai/_guidelines_header.md +70 -0
- package/templates/base/_ai/context/README.md +20 -0
- package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
- package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
- package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
- package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
- package/templates/base/_ai/skills/README.md +80 -0
- package/templates/base/_ai/skills/TEMPLATE.md +106 -0
- package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
- package/templates/base/_ai/skills/debug/SKILL.md +93 -0
- package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
- package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
- package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
- package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
- package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
- package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
- package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
- package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
- package/templates/base/_claude/settings.local.json +37 -0
- package/templates/base/_cursor/rules/global.mdc +7 -0
- package/templates/base/_editorconfig +18 -0
- package/templates/base/_gemini/settings.json +3 -0
- package/templates/base/_github/copilot-instructions.md +1 -0
- package/templates/base/_github/pull_request_template.md +23 -0
- package/templates/base/_gitignore +22 -0
- package/templates/base/_junie/guidelines.md +1 -0
- package/templates/base/commit-instructions.md +92 -0
- package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
- package/templates/packs/docker/_guidelines.md +10 -0
- package/templates/packs/docker/pack.json +8 -0
- package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
- package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
- package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
- package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
- package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
- package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
- package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
- package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
- package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
- package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
- package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
- package/templates/packs/laravel/_guidelines.md +25 -0
- package/templates/packs/laravel/pack.json +6 -0
- package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
- package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
- package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
- package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
- package/templates/packs/playwright/_guidelines.md +12 -0
- package/templates/packs/playwright/pack.json +9 -0
- package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
- package/templates/packs/storybook/pack.json +6 -0
- package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
- package/templates/packs/vitest/pack.json +6 -0
- package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
- package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
- package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
- package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
- package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
- package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
- package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
- package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
- package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
- package/templates/packs/vue3/_guidelines.md +45 -0
- package/templates/packs/vue3/pack.json +11 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.test.ts,**/*.spec.ts"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# VITEST Testing Instructions
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Test Structure and Organization](#test-structure-and-organization)
|
|
10
|
+
2. [Test Naming and Descriptions](#test-naming-and-descriptions)
|
|
11
|
+
3. [Test Setup and Configuration](#test-setup-and-configuration)
|
|
12
|
+
4. [Component Testing Patterns](#component-testing-patterns)
|
|
13
|
+
5. [Test Coverage Requirements](#test-coverage-requirements)
|
|
14
|
+
6. [Performance and Best Practices](#performance-and-best-practices)
|
|
15
|
+
7. [Mocking and Stubbing](#mocking-and-stubbing)
|
|
16
|
+
8. [Accessibility Testing](#accessibility-testing)
|
|
17
|
+
9. [State and Event Testing](#state-and-event-testing)
|
|
18
|
+
10. [Test Documentation](#test-documentation)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Test Structure and Organization
|
|
23
|
+
|
|
24
|
+
### File Organization
|
|
25
|
+
- Place test files next to the component: `ComponentName.test.ts`
|
|
26
|
+
- Use descriptive test file names: `InputText.test.ts`, `Button.test.ts`
|
|
27
|
+
- Group related tests in `describe` blocks
|
|
28
|
+
- Use nested `describe` blocks for logical grouping
|
|
29
|
+
|
|
30
|
+
### Test Structure Template
|
|
31
|
+
```typescript
|
|
32
|
+
import { mount, VueWrapper } from '@vue/test-utils';
|
|
33
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
34
|
+
import ComponentName from './ComponentName.vue';
|
|
35
|
+
|
|
36
|
+
describe('ComponentName Component', () => {
|
|
37
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentName>>;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
wrapper = mount(ComponentName, {
|
|
41
|
+
props: {
|
|
42
|
+
// Required props only
|
|
43
|
+
dataTestId: 'test-component',
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Basic Rendering', () => {
|
|
49
|
+
// Basic rendering tests
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Props Testing', () => {
|
|
53
|
+
// Props validation tests
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('States', () => {
|
|
57
|
+
// State-based tests (disabled, error, loading, etc.)
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Event Handling', () => {
|
|
61
|
+
// Event emission and handling tests
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Accessibility', () => {
|
|
65
|
+
// Accessibility tests
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('Edge Cases', () => {
|
|
69
|
+
// Edge case and error handling tests
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Component Integration', () => {
|
|
73
|
+
// Complex integration tests
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Test Naming and Descriptions
|
|
81
|
+
|
|
82
|
+
### Test Naming Conventions
|
|
83
|
+
- Use descriptive test names that explain the expected behavior
|
|
84
|
+
- Start with "should" for behavior descriptions
|
|
85
|
+
- Use present tense: "should render correctly" not "should have rendered"
|
|
86
|
+
- Be specific about conditions: "should be disabled when disabled prop is true"
|
|
87
|
+
|
|
88
|
+
### Good Test Names Examples
|
|
89
|
+
```typescript
|
|
90
|
+
it('should render input element', () => {});
|
|
91
|
+
it('should have correct name attribute', () => {});
|
|
92
|
+
it('should be disabled when disabled prop is true', () => {});
|
|
93
|
+
it('should emit click event when clicked', () => {});
|
|
94
|
+
it('should handle null modelValue gracefully', () => {});
|
|
95
|
+
it('should apply error class when error prop is true', () => {});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Bad Test Names Examples
|
|
99
|
+
```typescript
|
|
100
|
+
it('works', () => {}); // Too vague
|
|
101
|
+
it('test input', () => {}); // Not descriptive
|
|
102
|
+
it('should have rendered', () => {}); // Past tense
|
|
103
|
+
it('input test', () => {}); // Not a sentence
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Test Setup and Configuration
|
|
109
|
+
|
|
110
|
+
### Required Imports
|
|
111
|
+
```typescript
|
|
112
|
+
import { mount, VueWrapper } from '@vue/test-utils';
|
|
113
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
114
|
+
import { nextTick } from 'vue'; // When needed for async operations
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Component Mounting
|
|
118
|
+
- Always provide required props in `beforeEach`
|
|
119
|
+
- Use `dataTestId` for test identification
|
|
120
|
+
- Set up minimal required props only
|
|
121
|
+
- Use `VueWrapper<InstanceType<typeof Component>>` for type safety
|
|
122
|
+
|
|
123
|
+
### Test Configuration
|
|
124
|
+
```typescript
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
wrapper = mount(ComponentName, {
|
|
127
|
+
props: {
|
|
128
|
+
// Only required props
|
|
129
|
+
name: 'test-component',
|
|
130
|
+
dataTestId: 'test-component',
|
|
131
|
+
},
|
|
132
|
+
// Add slots if needed
|
|
133
|
+
slots: {
|
|
134
|
+
default: 'Test Content',
|
|
135
|
+
},
|
|
136
|
+
// Add global plugins if needed
|
|
137
|
+
global: {
|
|
138
|
+
plugins: [i18n, router],
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Component Testing Patterns
|
|
147
|
+
|
|
148
|
+
### Basic Rendering Tests
|
|
149
|
+
Test fundamental component structure and attributes:
|
|
150
|
+
```typescript
|
|
151
|
+
describe('Basic Rendering', () => {
|
|
152
|
+
it('should render main element', () => {
|
|
153
|
+
expect(wrapper.element.tagName).toBe('BUTTON');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should have correct data-testid attribute', () => {
|
|
157
|
+
expect(wrapper.attributes('data-testid')).toBe('test-button');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should have base CSS class', () => {
|
|
161
|
+
expect(wrapper.classes()).toContain('button');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Props Testing
|
|
167
|
+
Test all component props systematically:
|
|
168
|
+
```typescript
|
|
169
|
+
describe('Props Testing', () => {
|
|
170
|
+
it('should set type attribute correctly', async () => {
|
|
171
|
+
await wrapper.setProps({ type: 'email' });
|
|
172
|
+
const input = wrapper.find('input');
|
|
173
|
+
expect(input.attributes('type')).toBe('email');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should default to text type', () => {
|
|
177
|
+
const input = wrapper.find('input');
|
|
178
|
+
expect(input.attributes('type')).toBe('text');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should handle null values gracefully', async () => {
|
|
182
|
+
await wrapper.setProps({ icon: null });
|
|
183
|
+
expect(wrapper.find('.icon').exists()).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### State Testing
|
|
189
|
+
Test component states (disabled, error, loading, etc.):
|
|
190
|
+
```typescript
|
|
191
|
+
describe('States', () => {
|
|
192
|
+
it('should be disabled when disabled prop is true', async () => {
|
|
193
|
+
await wrapper.setProps({ disabled: true });
|
|
194
|
+
expect(wrapper.attributes('disabled')).toBeDefined();
|
|
195
|
+
expect(wrapper.classes()).toContain('is-disabled');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should have error class when error prop is true', async () => {
|
|
199
|
+
await wrapper.setProps({ error: true });
|
|
200
|
+
expect(wrapper.classes()).toContain('is-error');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Event Testing
|
|
206
|
+
Test event emission and handling:
|
|
207
|
+
```typescript
|
|
208
|
+
describe('Event Handling', () => {
|
|
209
|
+
it('should emit click event when clicked', async () => {
|
|
210
|
+
await wrapper.trigger('click');
|
|
211
|
+
expect(wrapper.emitted('click')).toBeTruthy();
|
|
212
|
+
expect(wrapper.emitted('click')).toHaveLength(1);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should not emit click event when disabled', async () => {
|
|
216
|
+
await wrapper.setProps({ disabled: true });
|
|
217
|
+
await wrapper.trigger('click');
|
|
218
|
+
expect(wrapper.emitted('click')).toBeFalsy();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Test Coverage Requirements
|
|
226
|
+
|
|
227
|
+
### Required Test Categories
|
|
228
|
+
Every component must have tests for:
|
|
229
|
+
|
|
230
|
+
1. **Basic Rendering** - Element structure, attributes, classes
|
|
231
|
+
2. **Props Testing** - All props, default values, null handling
|
|
232
|
+
3. **States** - Disabled, error, loading, required states
|
|
233
|
+
4. **Event Handling** - Click, input, change events
|
|
234
|
+
5. **Accessibility** - ARIA attributes, keyboard navigation
|
|
235
|
+
6. **Edge Cases** - Null values, empty strings, boundary conditions
|
|
236
|
+
7. **Component Integration** - Complex prop combinations
|
|
237
|
+
|
|
238
|
+
### Coverage Targets
|
|
239
|
+
- **100% line coverage** for all components in authentication and security-critical modules (login, password reset, registration)
|
|
240
|
+
- **Minimum 90% line coverage** for all other components
|
|
241
|
+
- **100% coverage** for critical business logic, composables, and services
|
|
242
|
+
- **All public props and events** must be tested
|
|
243
|
+
- **All conditional rendering** must be tested
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Performance and Best Practices
|
|
248
|
+
|
|
249
|
+
### Test Performance
|
|
250
|
+
- Use `beforeEach` for common setup
|
|
251
|
+
- Avoid unnecessary DOM queries
|
|
252
|
+
- Use `async/await` for prop changes
|
|
253
|
+
- Use `nextTick()` for DOM updates after reactive changes
|
|
254
|
+
|
|
255
|
+
### Critical: Always use await nextTick() after setProps
|
|
256
|
+
When testing Vue components, **always use `await nextTick()` after `setProps()`** when checking DOM changes or styles:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { nextTick } from 'vue';
|
|
260
|
+
|
|
261
|
+
// CORRECT: Use await nextTick() after setProps
|
|
262
|
+
it('should update DOM when prop changes', async () => {
|
|
263
|
+
await wrapper.setProps({ disabled: true });
|
|
264
|
+
await nextTick(); // Wait for Vue to update DOM
|
|
265
|
+
expect(wrapper.attributes('disabled')).toBeDefined();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should apply correct styles after prop change', async () => {
|
|
269
|
+
await wrapper.setProps({ items: newItems });
|
|
270
|
+
await nextTick(); // Wait for computed properties and DOM updates
|
|
271
|
+
const items = wrapper.findAll('.grid-item');
|
|
272
|
+
expect(items).toHaveLength(2);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// WRONG: Missing await nextTick() - test may fail intermittently
|
|
276
|
+
it('should update DOM when prop changes', async () => {
|
|
277
|
+
await wrapper.setProps({ disabled: true });
|
|
278
|
+
// Missing nextTick() - DOM may not be updated yet!
|
|
279
|
+
expect(wrapper.attributes('disabled')).toBeDefined();
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Why is this important?**
|
|
284
|
+
- `await wrapper.setProps()` triggers Vue's reactivity system
|
|
285
|
+
- Computed properties, watchers, and DOM updates happen in the **next tick**
|
|
286
|
+
- Without `await nextTick()`, tests check DOM before Vue finishes rendering
|
|
287
|
+
- This causes intermittent test failures that are hard to debug
|
|
288
|
+
|
|
289
|
+
### Best Practices
|
|
290
|
+
```typescript
|
|
291
|
+
// Good: Use async/await for prop changes
|
|
292
|
+
it('should update when prop changes', async () => {
|
|
293
|
+
await wrapper.setProps({ disabled: true });
|
|
294
|
+
expect(wrapper.attributes('disabled')).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Good: Use nextTick for DOM updates
|
|
298
|
+
it('should update DOM after value change', async () => {
|
|
299
|
+
await wrapper.setProps({ modelValue: 'new value' });
|
|
300
|
+
await nextTick();
|
|
301
|
+
expect(wrapper.find('input').element.value).toBe('new value');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Bad: Don't forget await
|
|
305
|
+
it('should update when prop changes', () => {
|
|
306
|
+
wrapper.setProps({ disabled: true }); // Missing await
|
|
307
|
+
expect(wrapper.attributes('disabled')).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Test Isolation
|
|
312
|
+
- Each test should be independent
|
|
313
|
+
- Use `beforeEach` for setup, not shared state
|
|
314
|
+
- Clean up after tests if needed
|
|
315
|
+
- Don't rely on test execution order
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Mocking and Stubbing
|
|
320
|
+
|
|
321
|
+
### Component Mocking
|
|
322
|
+
Mock external components and dependencies:
|
|
323
|
+
```typescript
|
|
324
|
+
// Mock external components
|
|
325
|
+
vi.mock('{{COMPONENT_LIB_IMPORT}}', () => ({
|
|
326
|
+
Button: {
|
|
327
|
+
name: 'Button',
|
|
328
|
+
template: '<button class="button-mock"><slot /></button>',
|
|
329
|
+
props: ['variant', 'icon', 'dataTestId'],
|
|
330
|
+
emits: ['click'],
|
|
331
|
+
},
|
|
332
|
+
}));
|
|
333
|
+
|
|
334
|
+
// Mock third-party libraries
|
|
335
|
+
vi.mock('ag-grid-vue3', () => ({
|
|
336
|
+
AgGridVue: {
|
|
337
|
+
name: 'AgGridVue',
|
|
338
|
+
template: '<div class="ag-grid-mock"></div>',
|
|
339
|
+
props: ['rowData', 'columnDefs'],
|
|
340
|
+
},
|
|
341
|
+
}));
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Global Mocks
|
|
345
|
+
Set up global mocks in test setup:
|
|
346
|
+
```typescript
|
|
347
|
+
// Mock window.matchMedia
|
|
348
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
349
|
+
writable: true,
|
|
350
|
+
value: vi.fn().mockImplementation((query: string) => ({
|
|
351
|
+
matches: false,
|
|
352
|
+
media: query,
|
|
353
|
+
onchange: null,
|
|
354
|
+
addListener: vi.fn(),
|
|
355
|
+
removeListener: vi.fn(),
|
|
356
|
+
})),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Mock ResizeObserver
|
|
360
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
361
|
+
observe: vi.fn(),
|
|
362
|
+
unobserve: vi.fn(),
|
|
363
|
+
disconnect: vi.fn(),
|
|
364
|
+
}));
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### API and Service Mocking
|
|
368
|
+
```typescript
|
|
369
|
+
// Mock API calls
|
|
370
|
+
vi.mock('{{MODULE_PATH}}/services/api', () => ({
|
|
371
|
+
fetchData: vi.fn().mockResolvedValue({ data: [] }),
|
|
372
|
+
postData: vi.fn().mockResolvedValue({ success: true }),
|
|
373
|
+
}));
|
|
374
|
+
|
|
375
|
+
// Mock composables
|
|
376
|
+
vi.mock('@/composables/useApi', () => ({
|
|
377
|
+
useApi: () => ({
|
|
378
|
+
loading: ref(false),
|
|
379
|
+
error: ref(null),
|
|
380
|
+
data: ref([]),
|
|
381
|
+
fetch: vi.fn(),
|
|
382
|
+
}),
|
|
383
|
+
}));
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Accessibility Testing
|
|
389
|
+
|
|
390
|
+
### Required Accessibility Tests
|
|
391
|
+
Every component must test **actual** accessibility features (not just data-testid):
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
describe('Accessibility', () => {
|
|
395
|
+
it('should have required attribute when required is true', async () => {
|
|
396
|
+
await wrapper.setProps({ required: true });
|
|
397
|
+
const input = wrapper.find('input');
|
|
398
|
+
expect(input.attributes('required')).toBeDefined();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should have disabled attribute when disabled is true', async () => {
|
|
402
|
+
await wrapper.setProps({ disabled: true });
|
|
403
|
+
const input = wrapper.find('input');
|
|
404
|
+
expect(input.attributes('disabled')).toBeDefined();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should have aria-hidden on decorative icons', async () => {
|
|
408
|
+
await wrapper.setProps({ icon: 'search' });
|
|
409
|
+
const icon = wrapper.find('.icon');
|
|
410
|
+
expect(icon.attributes('aria-hidden')).toBe('true');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should have proper aria-label for screen readers', () => {
|
|
414
|
+
const button = wrapper.find('button');
|
|
415
|
+
expect(button.attributes('aria-label')).toBe('Submit form');
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Important: Accessibility vs Component Integration
|
|
421
|
+
**Do not confuse accessibility tests with component integration tests:**
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// BAD: This is NOT an accessibility test
|
|
425
|
+
describe('Accessibility', () => {
|
|
426
|
+
it('should have data-testid', () => {
|
|
427
|
+
expect(wrapper.attributes('data-testid')).toBe('test-component');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should pass props to child components', () => {
|
|
431
|
+
const child = wrapper.findComponent(ChildComponent);
|
|
432
|
+
expect(child.props('data')).toBeDefined();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// GOOD: Rename to Component Integration
|
|
437
|
+
describe('Component Integration', () => {
|
|
438
|
+
it('should have proper data-testid for testing', () => {
|
|
439
|
+
expect(wrapper.attributes('data-testid')).toBe('test-component');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should pass item data to child components', () => {
|
|
443
|
+
const child = wrapper.findComponent(ChildComponent);
|
|
444
|
+
expect(child.props('item')).toEqual(mockData);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### ARIA Testing
|
|
450
|
+
- Test `aria-hidden` on decorative elements
|
|
451
|
+
- Test `aria-label` and `aria-describedby`
|
|
452
|
+
- Test `role` attributes where applicable
|
|
453
|
+
- Test keyboard navigation support (Tab, Enter, Escape)
|
|
454
|
+
- Test focus management
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## State and Event Testing
|
|
459
|
+
|
|
460
|
+
### v-model Testing
|
|
461
|
+
Test two-way data binding:
|
|
462
|
+
```typescript
|
|
463
|
+
describe('v-model Functionality', () => {
|
|
464
|
+
it('should update modelValue when input value changes', async () => {
|
|
465
|
+
const input = wrapper.find('input');
|
|
466
|
+
await input.setValue('test value');
|
|
467
|
+
expect(wrapper.vm.modelValue).toBe('test value');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should update input value when modelValue changes', async () => {
|
|
471
|
+
await wrapper.setProps({ modelValue: 'new value' });
|
|
472
|
+
const input = wrapper.find('input');
|
|
473
|
+
expect(input.element.value).toBe('new value');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should handle null modelValue', async () => {
|
|
477
|
+
await wrapper.setProps({ modelValue: null });
|
|
478
|
+
const input = wrapper.find('input');
|
|
479
|
+
expect(input.element.value).toBe('');
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Event Emission Testing
|
|
485
|
+
```typescript
|
|
486
|
+
describe('Event Handling', () => {
|
|
487
|
+
it('should emit update:modelValue with correct value', async () => {
|
|
488
|
+
const input = wrapper.find('input');
|
|
489
|
+
await input.setValue('test');
|
|
490
|
+
await input.trigger('input');
|
|
491
|
+
|
|
492
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
493
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['test']);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should emit multiple events in sequence', async () => {
|
|
497
|
+
await wrapper.trigger('click');
|
|
498
|
+
await wrapper.trigger('focus');
|
|
499
|
+
|
|
500
|
+
expect(wrapper.emitted('click')).toHaveLength(1);
|
|
501
|
+
expect(wrapper.emitted('focus')).toHaveLength(1);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Test Documentation
|
|
509
|
+
|
|
510
|
+
### Test Comments
|
|
511
|
+
- Add comments for complex test logic
|
|
512
|
+
- Explain why certain assertions are needed
|
|
513
|
+
- Document edge cases and their reasoning
|
|
514
|
+
|
|
515
|
+
### Test Organization
|
|
516
|
+
```typescript
|
|
517
|
+
describe('ComponentName Component', () => {
|
|
518
|
+
// Basic functionality tests
|
|
519
|
+
describe('Basic Rendering', () => {
|
|
520
|
+
// Tests for fundamental component structure
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// Props and configuration tests
|
|
524
|
+
describe('Props Testing', () => {
|
|
525
|
+
// Tests for all component props
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// State-based behavior tests
|
|
529
|
+
describe('States', () => {
|
|
530
|
+
// Tests for different component states
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// User interaction tests
|
|
534
|
+
describe('Event Handling', () => {
|
|
535
|
+
// Tests for user interactions and events
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Accessibility compliance tests
|
|
539
|
+
describe('Accessibility', () => {
|
|
540
|
+
// Tests for accessibility features
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Error handling and edge cases
|
|
544
|
+
describe('Edge Cases', () => {
|
|
545
|
+
// Tests for boundary conditions and error states
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Complex integration scenarios
|
|
549
|
+
describe('Component Integration', () => {
|
|
550
|
+
// Tests for complex prop combinations and interactions
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Test Documentation Requirements
|
|
556
|
+
- Each test file should have a clear purpose
|
|
557
|
+
- Complex test logic should be documented
|
|
558
|
+
- Edge cases should be explained
|
|
559
|
+
- Integration tests should describe the scenario
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Testing Checklist
|
|
564
|
+
|
|
565
|
+
### Before Writing Tests
|
|
566
|
+
- [ ] Understand component requirements and props
|
|
567
|
+
- [ ] Identify all public API (props, events, slots)
|
|
568
|
+
- [ ] Plan test structure and organization
|
|
569
|
+
- [ ] Identify dependencies to mock
|
|
570
|
+
|
|
571
|
+
### During Test Writing
|
|
572
|
+
- [ ] Test all props with valid and invalid values
|
|
573
|
+
- [ ] Test all component states (disabled, error, loading)
|
|
574
|
+
- [ ] Test all event emissions
|
|
575
|
+
- [ ] Test accessibility features
|
|
576
|
+
- [ ] Test edge cases and error conditions
|
|
577
|
+
- [ ] Use descriptive test names
|
|
578
|
+
- [ ] Follow async/await patterns
|
|
579
|
+
|
|
580
|
+
### After Writing Tests
|
|
581
|
+
- [ ] Run tests to ensure they pass
|
|
582
|
+
- [ ] Check test coverage meets requirements
|
|
583
|
+
- [ ] Verify tests are independent and isolated
|
|
584
|
+
- [ ] Review test names for clarity
|
|
585
|
+
- [ ] Ensure all edge cases are covered
|
|
586
|
+
|
|
587
|
+
### Quality Gates
|
|
588
|
+
- [ ] All tests pass consistently
|
|
589
|
+
- [ ] Test coverage >= 80%
|
|
590
|
+
- [ ] No flaky or intermittent tests
|
|
591
|
+
- [ ] Tests run in reasonable time
|
|
592
|
+
- [ ] Tests are maintainable and readable
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Common Testing Anti-Patterns
|
|
597
|
+
|
|
598
|
+
### Don't Do This
|
|
599
|
+
```typescript
|
|
600
|
+
// Don't test implementation details
|
|
601
|
+
it('should call handleClick method', () => {
|
|
602
|
+
const spy = vi.spyOn(wrapper.vm, 'handleClick');
|
|
603
|
+
wrapper.trigger('click');
|
|
604
|
+
expect(spy).toHaveBeenCalled();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// Don't use magic numbers without context
|
|
608
|
+
it('should have correct width', () => {
|
|
609
|
+
expect(wrapper.element.style.width).toBe('100px');
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Don't test multiple things in one test
|
|
613
|
+
it('should render and handle click and emit event', () => {
|
|
614
|
+
expect(wrapper.exists()).toBe(true);
|
|
615
|
+
wrapper.trigger('click');
|
|
616
|
+
expect(wrapper.emitted('click')).toBeTruthy();
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Do This Instead
|
|
621
|
+
```typescript
|
|
622
|
+
// Test behavior, not implementation
|
|
623
|
+
it('should emit click event when clicked', async () => {
|
|
624
|
+
await wrapper.trigger('click');
|
|
625
|
+
expect(wrapper.emitted('click')).toBeTruthy();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Use descriptive assertions
|
|
629
|
+
it('should have full width when wide prop is true', async () => {
|
|
630
|
+
await wrapper.setProps({ wide: true });
|
|
631
|
+
expect(wrapper.classes()).toContain('is-wide');
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Test one thing per test
|
|
635
|
+
it('should render button element', () => {
|
|
636
|
+
expect(wrapper.element.tagName).toBe('BUTTON');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('should emit click event when clicked', async () => {
|
|
640
|
+
await wrapper.trigger('click');
|
|
641
|
+
expect(wrapper.emitted('click')).toBeTruthy();
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Additional Resources
|
|
648
|
+
|
|
649
|
+
### VITEST Documentation
|
|
650
|
+
- [VITEST Testing Framework](https://vitest.dev/)
|
|
651
|
+
- [Vue Test Utils](https://test-utils.vuejs.org/)
|
|
652
|
+
- [Testing Vue Components](https://vuejs.org/guide/scaling-up/testing.html)
|
|
653
|
+
|
|
654
|
+
### Project-Specific Testing
|
|
655
|
+
- Use existing test files as examples
|
|
656
|
+
- Follow established patterns in the codebase
|
|
657
|
+
- Maintain consistency with existing test structure
|
|
658
|
+
- Use project-specific mocks and utilities
|
|
659
|
+
|
|
660
|
+
### Testing Tools
|
|
661
|
+
- VITEST for test runner and assertions
|
|
662
|
+
- Vue Test Utils for component testing
|
|
663
|
+
- jsdom for DOM simulation
|
|
664
|
+
- Mock Service Worker for API mocking (if needed)
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Essential Test Commands
|
|
669
|
+
|
|
670
|
+
### Development Commands
|
|
671
|
+
```bash
|
|
672
|
+
npm run test # Run tests in watch mode
|
|
673
|
+
npm run test:run # Run tests once
|
|
674
|
+
npm run test:coverage # Generate coverage report
|
|
675
|
+
npm run test:ui # Open Vitest UI
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### CI/CD Commands
|
|
679
|
+
```bash
|
|
680
|
+
npm run test:ci # Run tests in CI mode
|
|
681
|
+
npm run test:coverage:ci # Generate coverage for CI
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Debugging Tests
|
|
685
|
+
```bash
|
|
686
|
+
npm run test -- --reporter=verbose # Verbose output
|
|
687
|
+
npm run test -- --run --reporter=json # JSON output for parsing
|
|
688
|
+
```
|