mdk-skills 2.2.0 → 2.2.2
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/.claude/.install.log +31 -0
- package/.claude/backups/20260510.145547/.install.log +18 -0
- package/.claude/backups/20260510.145547/skills/test1/.meta.json +6 -0
- package/.claude/backups/20260510.145547/skills/test2/.meta.json +6 -0
- package/.claude/backups/20260510.145651/.install.log +19 -0
- package/.claude/backups/20260510.145651/profiles.json +67 -0
- package/.claude/backups/20260510.145651/settings.json +29 -0
- package/.claude/backups/20260510.145651/skills/frontend-code-review/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/frontend-code-review/SKILL.md +167 -0
- package/.claude/backups/20260510.145651/skills/frontend-code-review/references/checklist.md +298 -0
- package/.claude/backups/20260510.145651/skills/frontend-design/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/backups/20260510.145651/skills/frontend-design/SKILL.md +42 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/SKILL.md +356 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/references/output-patterns.md +82 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/references/workflows.md +28 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/backups/20260510.145651/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/.claude/backups/20260510.145651/skills/test1/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/test2/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/SKILL.md +228 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/scripts/core.py +238 -0
- package/.claude/backups/20260510.145651/skills/ui-ux-pro-max/scripts/search.py +61 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/SKILL.md +26 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/infinite-scroll.md +292 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/pinia-store.md +174 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/service-layer.md +198 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/tab-anchor.md +1125 -0
- package/.claude/backups/20260510.145651/skills/v3-fe-biz-patterns/references/use-loading.md +114 -0
- package/.claude/backups/20260510.145651/skills/vue/.meta.json +6 -0
- package/.claude/backups/20260510.145651/skills/vue/SKILL.md +103 -0
- package/.claude/backups/20260510.145651/skills/vue/references/components.md +323 -0
- package/.claude/backups/20260510.145651/skills/vue/references/composables.md +358 -0
- package/.claude/backups/20260510.145651/skills/vue/references/directives.md +225 -0
- package/.claude/backups/20260510.145651/skills/vue/references/gotchas.md +438 -0
- package/.claude/backups/20260510.145651/skills/vue/references/provide-inject.md +174 -0
- package/.claude/backups/20260510.145651/skills/vue/references/reactivity.md +289 -0
- package/.claude/backups/20260510.145651/skills/vue/references/router.md +181 -0
- package/.claude/backups/20260510.145651/skills/vue/references/testing.md +294 -0
- package/.claude/backups/20260510.145651/skills/vue/references/typescript.md +172 -0
- package/.claude/backups/20260510.145651/skills/vue/references/utils-client.md +156 -0
- package/.claude/backups/20260510.150310/.install.log +30 -0
- package/.claude/backups/20260510.150310/profiles.json +67 -0
- package/.claude/backups/20260510.150310/settings.json +29 -0
- package/.claude/backups/20260510.150310/skills/frontend-code-review/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/frontend-code-review/SKILL.md +167 -0
- package/.claude/backups/20260510.150310/skills/frontend-code-review/references/checklist.md +298 -0
- package/.claude/backups/20260510.150310/skills/frontend-design/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/backups/20260510.150310/skills/frontend-design/SKILL.md +42 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/SKILL.md +356 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/references/output-patterns.md +82 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/references/workflows.md +28 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/backups/20260510.150310/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/.claude/backups/20260510.150310/skills/test1/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/test2/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/SKILL.md +228 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/scripts/core.py +238 -0
- package/.claude/backups/20260510.150310/skills/ui-ux-pro-max/scripts/search.py +61 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/SKILL.md +26 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/infinite-scroll.md +292 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/pinia-store.md +174 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/service-layer.md +198 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/tab-anchor.md +1125 -0
- package/.claude/backups/20260510.150310/skills/v3-fe-biz-patterns/references/use-loading.md +114 -0
- package/.claude/backups/20260510.150310/skills/vue/.meta.json +6 -0
- package/.claude/backups/20260510.150310/skills/vue/SKILL.md +103 -0
- package/.claude/backups/20260510.150310/skills/vue/references/components.md +323 -0
- package/.claude/backups/20260510.150310/skills/vue/references/composables.md +358 -0
- package/.claude/backups/20260510.150310/skills/vue/references/directives.md +225 -0
- package/.claude/backups/20260510.150310/skills/vue/references/gotchas.md +438 -0
- package/.claude/backups/20260510.150310/skills/vue/references/provide-inject.md +174 -0
- package/.claude/backups/20260510.150310/skills/vue/references/reactivity.md +289 -0
- package/.claude/backups/20260510.150310/skills/vue/references/router.md +181 -0
- package/.claude/backups/20260510.150310/skills/vue/references/testing.md +294 -0
- package/.claude/backups/20260510.150310/skills/vue/references/typescript.md +172 -0
- package/.claude/backups/20260510.150310/skills/vue/references/utils-client.md +156 -0
- package/.claude/backups/CLAUDE.md.20260510.145155 +131 -0
- package/.claude/backups/CLAUDE.md.20260510.145651 +131 -0
- package/.claude/backups/CLAUDE.md.20260510.150310 +131 -0
- package/.claude/settings.json +1 -7
- package/.claude/skills/test1/.meta.json +6 -0
- package/.claude/skills/test2/.meta.json +6 -0
- package/package.json +1 -1
- package/scripts/cli.js +1 -1
- package/scripts/web-ui/server.js +2 -1
- package/scripts/web-ui/src/App.vue +1 -2
- package/scripts/web-ui/src/api/skills.js +5 -1
- package/scripts/web-ui/src/views/Dashboard.vue +12 -1
- package/.claude/backups/20260510.144501/.install.log +0 -1
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/profiles.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/settings.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-code-review/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-code-review/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-code-review/references/checklist.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-design/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-design/LICENSE.txt +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/frontend-design/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/references/output-patterns.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/references/workflows.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/scripts/init_skill.py +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/scripts/package_skill.py +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/skill-creator/scripts/quick_validate.py +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/charts.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/colors.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/landing.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/products.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/prompts.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/react.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/styles.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/typography.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/scripts/core.py +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/ui-ux-pro-max/scripts/search.py +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/references/infinite-scroll.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/references/pinia-store.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/references/service-layer.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/references/tab-anchor.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/v3-fe-biz-patterns/references/use-loading.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/.meta.json +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/SKILL.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/components.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/composables.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/directives.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/gotchas.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/provide-inject.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/reactivity.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/router.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/testing.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/typescript.md +0 -0
- /package/.claude/backups/{20260510.144501 → 20260510.145547}/skills/vue/references/utils-client.md +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Vue Testing
|
|
2
|
+
|
|
3
|
+
Test patterns for Vue 3 components, composables, and utilities.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Test Type | Pattern |
|
|
8
|
+
| ---------------- | ------------------------------------ |
|
|
9
|
+
| Component | `mount(Component, { props, slots })` |
|
|
10
|
+
| User interaction | `await wrapper.trigger('click')` |
|
|
11
|
+
| Emitted events | `wrapper.emitted('update')` |
|
|
12
|
+
| Composable | Call directly, test return values |
|
|
13
|
+
| Utils | Pure function testing (easiest) |
|
|
14
|
+
|
|
15
|
+
## Stack
|
|
16
|
+
|
|
17
|
+
- **Vitest** - test runner
|
|
18
|
+
- **@vue/test-utils** - component mounting, interaction
|
|
19
|
+
- **@testing-library/vue** - user-centric alternative
|
|
20
|
+
- **happy-dom / jsdom** - DOM environment
|
|
21
|
+
|
|
22
|
+
## File Location
|
|
23
|
+
|
|
24
|
+
Colocate tests with code:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Button.vue → Button.spec.ts
|
|
28
|
+
useAuth.ts → useAuth.spec.ts
|
|
29
|
+
formatters.ts → formatters.spec.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Component Tests
|
|
33
|
+
|
|
34
|
+
### Basic
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { mount } from '@vue/test-utils'
|
|
38
|
+
import Button from './Button.vue'
|
|
39
|
+
|
|
40
|
+
it('renders slot', () => {
|
|
41
|
+
const wrapper = mount(Button, {
|
|
42
|
+
slots: { default: 'Click me' }
|
|
43
|
+
})
|
|
44
|
+
expect(wrapper.text()).toBe('Click me')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('emits on click', async () => {
|
|
48
|
+
const wrapper = mount(Button)
|
|
49
|
+
await wrapper.trigger('click')
|
|
50
|
+
expect(wrapper.emitted('click')).toHaveLength(1)
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Props
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
it('applies variant class', () => {
|
|
58
|
+
const wrapper = mount(Button, {
|
|
59
|
+
props: { variant: 'primary' }
|
|
60
|
+
})
|
|
61
|
+
expect(wrapper.classes()).toContain('btn-primary')
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Emits
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
it('emits update with payload', async () => {
|
|
69
|
+
const wrapper = mount(Input)
|
|
70
|
+
await wrapper.find('input').setValue('new value')
|
|
71
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['new value'])
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Slots
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
it('renders named slots', () => {
|
|
79
|
+
const wrapper = mount(Card, {
|
|
80
|
+
slots: {
|
|
81
|
+
header: '<h1>Title</h1>',
|
|
82
|
+
default: '<p>Content</p>'
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
expect(wrapper.html()).toContain('<h1>Title</h1>')
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Composable Tests
|
|
90
|
+
|
|
91
|
+
Call directly, no mounting needed:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { useCounter } from './useCounter'
|
|
95
|
+
|
|
96
|
+
it('increments count', () => {
|
|
97
|
+
const { count, increment } = useCounter(0)
|
|
98
|
+
expect(count.value).toBe(0)
|
|
99
|
+
increment()
|
|
100
|
+
expect(count.value).toBe(1)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('resets to initial', () => {
|
|
104
|
+
const { count, increment, reset } = useCounter(5)
|
|
105
|
+
increment()
|
|
106
|
+
increment()
|
|
107
|
+
expect(count.value).toBe(7)
|
|
108
|
+
reset()
|
|
109
|
+
expect(count.value).toBe(5)
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Utils Tests
|
|
114
|
+
|
|
115
|
+
Easiest - pure functions:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { formatCurrency, slugify } from './formatters'
|
|
119
|
+
|
|
120
|
+
describe('formatCurrency', () => {
|
|
121
|
+
it('formats USD', () => {
|
|
122
|
+
expect(formatCurrency(10.5)).toBe('$10.50')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('slugify', () => {
|
|
127
|
+
it('converts to lowercase', () => {
|
|
128
|
+
expect(slugify('Hello World')).toBe('hello-world')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('removes special chars', () => {
|
|
132
|
+
expect(slugify('Hello! World?')).toBe('hello-world')
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Mocking
|
|
138
|
+
|
|
139
|
+
**Composables:**
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { vi } from 'vitest'
|
|
143
|
+
|
|
144
|
+
vi.mock('./useAuth', () => ({
|
|
145
|
+
useAuth: vi.fn(() => ({
|
|
146
|
+
user: { id: 1, name: 'Test' },
|
|
147
|
+
isAuthenticated: true
|
|
148
|
+
}))
|
|
149
|
+
}))
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**API calls:**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
global.fetch = vi.fn(() =>
|
|
156
|
+
Promise.resolve({
|
|
157
|
+
json: () => Promise.resolve({ data: [] })
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Router Mocking
|
|
163
|
+
|
|
164
|
+
Mock `useRoute` and `useRouter` for component tests:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { vi } from 'vitest'
|
|
168
|
+
import { mount } from '@vue/test-utils'
|
|
169
|
+
|
|
170
|
+
vi.mock('vue-router', () => ({
|
|
171
|
+
useRoute: vi.fn(() => ({
|
|
172
|
+
params: { id: '123' },
|
|
173
|
+
query: { filter: 'active' },
|
|
174
|
+
path: '/users/123',
|
|
175
|
+
})),
|
|
176
|
+
useRouter: vi.fn(() => ({
|
|
177
|
+
push: vi.fn(),
|
|
178
|
+
replace: vi.fn(),
|
|
179
|
+
})),
|
|
180
|
+
}))
|
|
181
|
+
|
|
182
|
+
it('uses route params', () => {
|
|
183
|
+
const wrapper = mount(UserPage)
|
|
184
|
+
expect(wrapper.text()).toContain('123')
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Dynamic route mocking per test:**
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { useRoute } from 'vue-router'
|
|
192
|
+
|
|
193
|
+
it('handles different routes', () => {
|
|
194
|
+
vi.mocked(useRoute).mockReturnValue({
|
|
195
|
+
params: { id: '456' },
|
|
196
|
+
} as any)
|
|
197
|
+
|
|
198
|
+
const wrapper = mount(UserPage)
|
|
199
|
+
expect(wrapper.text()).toContain('456')
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Suspense and Teleport
|
|
204
|
+
|
|
205
|
+
**Testing async components with Suspense:**
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
209
|
+
|
|
210
|
+
it('renders async content', async () => {
|
|
211
|
+
const wrapper = mount(AsyncComponent, {
|
|
212
|
+
global: {
|
|
213
|
+
stubs: { Suspense: false }, // Don't stub Suspense
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Wait for async setup to complete
|
|
218
|
+
await flushPromises()
|
|
219
|
+
|
|
220
|
+
expect(wrapper.text()).toContain('Loaded content')
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Testing Teleport:**
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
it('teleports modal content', () => {
|
|
228
|
+
const wrapper = mount(Modal, {
|
|
229
|
+
global: {
|
|
230
|
+
stubs: {
|
|
231
|
+
teleport: true, // Stub teleport to render inline
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
expect(wrapper.text()).toContain('Modal content')
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Access teleported content:**
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
it('finds teleported content', () => {
|
|
244
|
+
document.body.innerHTML = '<div id="modal-target"></div>'
|
|
245
|
+
|
|
246
|
+
mount(Modal, { props: { open: true } })
|
|
247
|
+
|
|
248
|
+
// Content teleports to #modal-target
|
|
249
|
+
expect(document.body.innerHTML).toContain('Modal content')
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Best Practices
|
|
254
|
+
|
|
255
|
+
**Do:**
|
|
256
|
+
|
|
257
|
+
- Test behavior (what user sees/does), not implementation
|
|
258
|
+
- Arrange-Act-Assert structure
|
|
259
|
+
- One assertion per test
|
|
260
|
+
- Descriptive test names
|
|
261
|
+
- Mock external dependencies
|
|
262
|
+
|
|
263
|
+
**Don't:**
|
|
264
|
+
|
|
265
|
+
- Test Vue internals (reactivity)
|
|
266
|
+
- Test third-party libraries
|
|
267
|
+
- Test trivial getters/setters
|
|
268
|
+
- Test implementation details
|
|
269
|
+
|
|
270
|
+
## What to Test
|
|
271
|
+
|
|
272
|
+
**Test:**
|
|
273
|
+
|
|
274
|
+
- User interactions (clicks, inputs)
|
|
275
|
+
- Conditional rendering
|
|
276
|
+
- Props validation, emitted events
|
|
277
|
+
- Computed values, business logic
|
|
278
|
+
|
|
279
|
+
**Skip:**
|
|
280
|
+
|
|
281
|
+
- Vue internals, third-party libs
|
|
282
|
+
- Trivial getters/setters
|
|
283
|
+
- Implementation details
|
|
284
|
+
|
|
285
|
+
## Running
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pnpm test # all
|
|
289
|
+
pnpm exec vitest Button.spec.ts # specific
|
|
290
|
+
pnpm exec vitest --watch # watch
|
|
291
|
+
pnpm test --coverage # coverage
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Docs:** [vitest.dev](https://vitest.dev/) · [test-utils.vuejs.org](https://test-utils.vuejs.org/)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Vue TypeScript Patterns
|
|
2
|
+
|
|
3
|
+
TypeScript-specific patterns for Vue 3 development.
|
|
4
|
+
|
|
5
|
+
## Provide/Inject Types
|
|
6
|
+
|
|
7
|
+
Use `InjectionKey` for type-safe dependency injection:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import type { InjectionKey } from 'vue'
|
|
11
|
+
import type { User } from './types'
|
|
12
|
+
|
|
13
|
+
// Define typed key
|
|
14
|
+
export const UserKey: InjectionKey<User> = Symbol('user')
|
|
15
|
+
|
|
16
|
+
// Provider component
|
|
17
|
+
const user = ref<User>({ id: 1, name: 'John' })
|
|
18
|
+
provide(UserKey, user)
|
|
19
|
+
|
|
20
|
+
// Consumer component
|
|
21
|
+
const user = inject(UserKey) // Ref<User> | undefined
|
|
22
|
+
const user = inject(UserKey)! // Ref<User> (assert non-null)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**With default value:**
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const user = inject(UserKey, ref({ id: 0, name: 'Guest' }))
|
|
29
|
+
// Type: Ref<User> (no undefined)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## vue-tsc Strict Templates
|
|
33
|
+
|
|
34
|
+
Enable stricter template type checking:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Check templates with strict mode
|
|
38
|
+
vue-tsc --noEmit --strict-templates
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Catches template errors like:
|
|
42
|
+
|
|
43
|
+
- Accessing non-existent properties
|
|
44
|
+
- Wrong prop types
|
|
45
|
+
- Missing required props
|
|
46
|
+
|
|
47
|
+
## tsconfig Settings
|
|
48
|
+
|
|
49
|
+
**Required for Vue 3:**
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"compilerOptions": {
|
|
54
|
+
"moduleResolution": "bundler",
|
|
55
|
+
"verbatimModuleSyntax": true,
|
|
56
|
+
"strict": true,
|
|
57
|
+
"jsx": "preserve"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**`moduleResolution: "bundler"`** - Matches Vite/webpack resolution. Avoids `.js` extension issues.
|
|
63
|
+
|
|
64
|
+
**`verbatimModuleSyntax: true`** - Enforces explicit `type` imports:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// ❌ May cause issues with bundlers
|
|
68
|
+
import { User } from './types'
|
|
69
|
+
|
|
70
|
+
// ✅ Explicit type import
|
|
71
|
+
import type { User } from './types'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Component Type Helpers
|
|
75
|
+
|
|
76
|
+
**Extract props type from component:**
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import type { ComponentProps, ComponentSlots, ComponentEmits } from 'vue-component-type-helpers'
|
|
80
|
+
import MyComponent from './MyComponent.vue'
|
|
81
|
+
|
|
82
|
+
type Props = ComponentProps<typeof MyComponent>
|
|
83
|
+
type Slots = ComponentSlots<typeof MyComponent>
|
|
84
|
+
type Emits = ComponentEmits<typeof MyComponent>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Extract exposed methods:**
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import type { ComponentExposed } from 'vue-component-type-helpers'
|
|
91
|
+
|
|
92
|
+
type Exposed = ComponentExposed<typeof MyComponent>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Generic Components
|
|
96
|
+
|
|
97
|
+
Define generic components with typed slots:
|
|
98
|
+
|
|
99
|
+
```vue
|
|
100
|
+
<script setup lang="ts" generic="T extends { id: string }">
|
|
101
|
+
defineProps<{
|
|
102
|
+
items: T[]
|
|
103
|
+
}>()
|
|
104
|
+
|
|
105
|
+
defineSlots<{
|
|
106
|
+
default: (props: { item: T }) => any
|
|
107
|
+
}>()
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<div v-for="item in items" :key="item.id">
|
|
112
|
+
<slot :item="item" />
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Ref Type Narrowing
|
|
118
|
+
|
|
119
|
+
Handle ref type narrowing correctly:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const maybeUser = ref<User | null>(null)
|
|
123
|
+
|
|
124
|
+
// ❌ TypeScript still sees User | null
|
|
125
|
+
if (maybeUser.value) {
|
|
126
|
+
maybeUser.value.name // Error: possibly null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ✅ Use computed or extract value
|
|
130
|
+
const userName = computed(() => maybeUser.value?.name ?? 'Guest')
|
|
131
|
+
|
|
132
|
+
// ✅ Or guard in same expression
|
|
133
|
+
maybeUser.value && maybeUser.value.name
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Event Handler Types
|
|
137
|
+
|
|
138
|
+
Type event handlers correctly:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// DOM events
|
|
142
|
+
const onClick = (e: MouseEvent) => { ... }
|
|
143
|
+
const onInput = (e: Event) => {
|
|
144
|
+
const target = e.target as HTMLInputElement
|
|
145
|
+
console.log(target.value)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Component emits
|
|
149
|
+
const onUpdate = (value: string) => { ... }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Common Mistakes
|
|
153
|
+
|
|
154
|
+
**Forgetting to import types explicitly:**
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// ❌ Runtime import of type-only
|
|
158
|
+
import { User } from './types'
|
|
159
|
+
|
|
160
|
+
// ✅ Type-only import
|
|
161
|
+
import type { User } from './types'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Not using `as const` for literal types:**
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// ❌ Type is string[]
|
|
168
|
+
const variants = ['primary', 'secondary']
|
|
169
|
+
|
|
170
|
+
// ✅ Type is readonly ['primary', 'secondary']
|
|
171
|
+
const variants = ['primary', 'secondary'] as const
|
|
172
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Client Utilities
|
|
2
|
+
|
|
3
|
+
Pure functions for formatting, validation, transformation, and parsing.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Category | Examples |
|
|
8
|
+
| ------------ | --------------------------------------------- |
|
|
9
|
+
| Formatters | `formatCurrency`, `formatDate`, `formatBytes` |
|
|
10
|
+
| Validators | `isValidEmail`, `isValidUrl`, `isValidPhone` |
|
|
11
|
+
| Transformers | `slugify`, `truncate`, `capitalize` |
|
|
12
|
+
| Parsers | `parseQuery`, `parseJSON`, `parseDate` |
|
|
13
|
+
|
|
14
|
+
## Rules
|
|
15
|
+
|
|
16
|
+
**Pure functions:**
|
|
17
|
+
|
|
18
|
+
- Same input → same output
|
|
19
|
+
- No side effects
|
|
20
|
+
- No external state mutation
|
|
21
|
+
- No API calls, no refs, no reactive
|
|
22
|
+
|
|
23
|
+
**When NOT to use utils:**
|
|
24
|
+
|
|
25
|
+
- Stateful logic → use composables
|
|
26
|
+
- Vue-specific → use composables
|
|
27
|
+
- Component logic → keep in component
|
|
28
|
+
- API calls → use queries
|
|
29
|
+
|
|
30
|
+
## Structure
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// utils/formatters.ts
|
|
34
|
+
export function formatCurrency(amount: number, currency = 'USD'): string {
|
|
35
|
+
return new Intl.NumberFormat('en-US', {
|
|
36
|
+
style: 'currency',
|
|
37
|
+
currency,
|
|
38
|
+
}).format(amount)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatRelativeTime(date: Date): string {
|
|
42
|
+
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
|
43
|
+
const diff = date.getTime() - Date.now()
|
|
44
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
|
45
|
+
return rtf.format(days, 'day')
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Naming:** Descriptive verbs (`formatCurrency`, `validateEmail`, `parseQuery`)
|
|
50
|
+
**Organization:** Group by category (`formatters.ts`, `validators.ts`)
|
|
51
|
+
**Exports:** Named exports only
|
|
52
|
+
|
|
53
|
+
## Examples by Category
|
|
54
|
+
|
|
55
|
+
**Formatters:**
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// utils/formatters.ts
|
|
59
|
+
export function formatBytes(bytes: number): string { ... }
|
|
60
|
+
export function formatPhone(phone: string): string { ... }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Validators:**
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// utils/validators.ts
|
|
67
|
+
export function isValidEmail(email: string): boolean {
|
|
68
|
+
return /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(email)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isValidUrl(url: string): boolean {
|
|
72
|
+
try { new URL(url); return true }
|
|
73
|
+
catch { return false }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Transformers:**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// utils/transformers.ts
|
|
81
|
+
export function slugify(text: string): string {
|
|
82
|
+
return text.toLowerCase()
|
|
83
|
+
.replace(/[^\w\s-]/g, '')
|
|
84
|
+
.replace(/\s+/g, '-')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function truncate(text: string, length: number): string {
|
|
88
|
+
return text.length > length ? `${text.slice(0, length)}...` : text
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Parsers:**
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// utils/parsers.ts
|
|
96
|
+
export function parseQuery(search: string): Record<string, string> {
|
|
97
|
+
return Object.fromEntries(new URLSearchParams(search))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function parseJSON<T>(json: string, fallback: T): T {
|
|
101
|
+
try { return JSON.parse(json) }
|
|
102
|
+
catch { return fallback }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Common Mistakes
|
|
107
|
+
|
|
108
|
+
**Side effects (not pure):**
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// ❌ Wrong - mutates external state
|
|
112
|
+
let count = 0
|
|
113
|
+
export function increment() {
|
|
114
|
+
count++
|
|
115
|
+
return count
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ✅ Correct - pure
|
|
119
|
+
export function add(a: number, b: number): number {
|
|
120
|
+
return a + b
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Using utils for stateful logic:**
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// ❌ Wrong - should be composable
|
|
128
|
+
export function useCounter() { ... }
|
|
129
|
+
|
|
130
|
+
// ✅ Correct - pure transformation
|
|
131
|
+
export function formatCount(count: number): string { ... }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Organization
|
|
135
|
+
|
|
136
|
+
**Flat for small projects:**
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
utils/
|
|
140
|
+
├── formatters.ts
|
|
141
|
+
├── validators.ts
|
|
142
|
+
└── transformers.ts
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Nested for large projects:**
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
utils/
|
|
149
|
+
├── formatters/
|
|
150
|
+
│ ├── date.ts
|
|
151
|
+
│ ├── currency.ts
|
|
152
|
+
│ └── index.ts
|
|
153
|
+
└── validators/
|
|
154
|
+
├── email.ts
|
|
155
|
+
└── index.ts
|
|
156
|
+
```
|