autoworkflow 3.1.4 → 3.5.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.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,368 @@
1
+ # TypeORM Skill
2
+
3
+ ## Data Source Configuration
4
+ \`\`\`typescript
5
+ // src/data-source.ts
6
+ import { DataSource } from 'typeorm';
7
+ import { User } from './entities/User';
8
+ import { Post } from './entities/Post';
9
+
10
+ export const AppDataSource = new DataSource({
11
+ type: 'postgres',
12
+ host: process.env.DB_HOST,
13
+ port: parseInt(process.env.DB_PORT || '5432'),
14
+ username: process.env.DB_USER,
15
+ password: process.env.DB_PASSWORD,
16
+ database: process.env.DB_NAME,
17
+ synchronize: false, // Use migrations in production!
18
+ logging: process.env.NODE_ENV === 'development',
19
+ entities: [User, Post],
20
+ migrations: ['src/migrations/*.ts'],
21
+ subscribers: ['src/subscribers/*.ts'],
22
+ });
23
+
24
+ // Initialize
25
+ await AppDataSource.initialize();
26
+ \`\`\`
27
+
28
+ ## Entity Definition
29
+ \`\`\`typescript
30
+ import {
31
+ Entity, PrimaryGeneratedColumn, Column, CreateDateColumn,
32
+ UpdateDateColumn, DeleteDateColumn, OneToMany, ManyToOne,
33
+ ManyToMany, JoinTable, Index, BeforeInsert, AfterLoad
34
+ } from 'typeorm';
35
+
36
+ @Entity('users')
37
+ @Index(['email'])
38
+ @Index(['createdAt'])
39
+ export class User {
40
+ @PrimaryGeneratedColumn('uuid')
41
+ id: string;
42
+
43
+ @Column({ unique: true })
44
+ email: string;
45
+
46
+ @Column()
47
+ name: string;
48
+
49
+ @Column({ select: false }) // Excluded from default selects
50
+ password: string;
51
+
52
+ @Column({ type: 'enum', enum: ['user', 'admin'], default: 'user' })
53
+ role: 'user' | 'admin';
54
+
55
+ @Column({ type: 'jsonb', nullable: true })
56
+ settings: Record<string, unknown>;
57
+
58
+ @OneToMany(() => Post, (post) => post.author)
59
+ posts: Post[];
60
+
61
+ @ManyToMany(() => Role, (role) => role.users)
62
+ @JoinTable({ name: 'user_roles' })
63
+ roles: Role[];
64
+
65
+ @CreateDateColumn()
66
+ createdAt: Date;
67
+
68
+ @UpdateDateColumn()
69
+ updatedAt: Date;
70
+
71
+ @DeleteDateColumn() // Soft delete
72
+ deletedAt: Date | null;
73
+
74
+ // Lifecycle hooks
75
+ @BeforeInsert()
76
+ async hashPassword() {
77
+ if (this.password) {
78
+ this.password = await bcrypt.hash(this.password, 10);
79
+ }
80
+ }
81
+
82
+ // Computed property (not persisted)
83
+ fullName: string;
84
+
85
+ @AfterLoad()
86
+ computeFullName() {
87
+ this.fullName = \`\${this.firstName} \${this.lastName}\`;
88
+ }
89
+ }
90
+
91
+ @Entity('posts')
92
+ export class Post {
93
+ @PrimaryGeneratedColumn('uuid')
94
+ id: string;
95
+
96
+ @Column()
97
+ title: string;
98
+
99
+ @Column({ type: 'text', nullable: true })
100
+ content: string;
101
+
102
+ @Column({ default: false })
103
+ published: boolean;
104
+
105
+ @ManyToOne(() => User, (user) => user.posts, { onDelete: 'CASCADE' })
106
+ author: User;
107
+
108
+ @Column()
109
+ authorId: string;
110
+
111
+ @CreateDateColumn()
112
+ createdAt: Date;
113
+ }
114
+ \`\`\`
115
+
116
+ ## Repository Patterns
117
+ \`\`\`typescript
118
+ // Get repository
119
+ const userRepository = AppDataSource.getRepository(User);
120
+
121
+ // Find operations
122
+ const user = await userRepository.findOne({
123
+ where: { id },
124
+ relations: ['posts', 'roles'],
125
+ });
126
+
127
+ const user = await userRepository.findOneOrFail({ where: { id } }); // Throws if not found
128
+
129
+ const users = await userRepository.find({
130
+ where: { role: 'user' },
131
+ relations: ['posts'],
132
+ order: { createdAt: 'DESC' },
133
+ skip: 0,
134
+ take: 20,
135
+ select: ['id', 'email', 'name', 'createdAt'],
136
+ });
137
+
138
+ // Complex where conditions
139
+ const users = await userRepository.find({
140
+ where: [
141
+ { role: 'admin' },
142
+ { email: Like('%@company.com') },
143
+ ], // OR condition
144
+ });
145
+
146
+ const users = await userRepository.find({
147
+ where: {
148
+ role: 'user',
149
+ createdAt: MoreThan(new Date('2024-01-01')),
150
+ }, // AND condition
151
+ });
152
+
153
+ // Create & Save
154
+ const user = userRepository.create({
155
+ email: 'user@example.com',
156
+ name: 'John',
157
+ });
158
+ await userRepository.save(user);
159
+
160
+ // Or in one step
161
+ const user = await userRepository.save({
162
+ email: 'user@example.com',
163
+ name: 'John',
164
+ });
165
+
166
+ // Update
167
+ await userRepository.update({ id }, { name: 'Jane' });
168
+
169
+ // Soft delete (with @DeleteDateColumn)
170
+ await userRepository.softDelete({ id });
171
+ await userRepository.restore({ id }); // Restore soft-deleted
172
+
173
+ // Hard delete
174
+ await userRepository.delete({ id });
175
+ \`\`\`
176
+
177
+ ## QueryBuilder
178
+ \`\`\`typescript
179
+ // Select query builder
180
+ const users = await userRepository
181
+ .createQueryBuilder('user')
182
+ .leftJoinAndSelect('user.posts', 'post')
183
+ .where('user.role = :role', { role: 'user' })
184
+ .andWhere('post.published = :published', { published: true })
185
+ .orderBy('user.createdAt', 'DESC')
186
+ .skip(0)
187
+ .take(20)
188
+ .getMany();
189
+
190
+ // With subquery
191
+ const usersWithPostCount = await userRepository
192
+ .createQueryBuilder('user')
193
+ .loadRelationCountAndMap('user.postCount', 'user.posts')
194
+ .getMany();
195
+
196
+ // Select specific fields
197
+ const userEmails = await userRepository
198
+ .createQueryBuilder('user')
199
+ .select(['user.id', 'user.email'])
200
+ .where('user.role = :role', { role: 'admin' })
201
+ .getRawMany();
202
+
203
+ // Aggregations
204
+ const stats = await userRepository
205
+ .createQueryBuilder('user')
206
+ .select('user.role', 'role')
207
+ .addSelect('COUNT(*)', 'count')
208
+ .groupBy('user.role')
209
+ .getRawMany();
210
+
211
+ // Update with query builder
212
+ await userRepository
213
+ .createQueryBuilder()
214
+ .update(User)
215
+ .set({ lastLoginAt: new Date() })
216
+ .where('id = :id', { id })
217
+ .execute();
218
+
219
+ // Delete with query builder
220
+ await userRepository
221
+ .createQueryBuilder()
222
+ .delete()
223
+ .from(User)
224
+ .where('deletedAt IS NOT NULL')
225
+ .andWhere('deletedAt < :date', { date: thirtyDaysAgo })
226
+ .execute();
227
+ \`\`\`
228
+
229
+ ## Transactions
230
+ \`\`\`typescript
231
+ // Using transaction manager
232
+ await AppDataSource.transaction(async (manager) => {
233
+ const userRepo = manager.getRepository(User);
234
+ const postRepo = manager.getRepository(Post);
235
+
236
+ const user = await userRepo.save({
237
+ email: 'test@example.com',
238
+ name: 'Test',
239
+ });
240
+
241
+ await postRepo.save({
242
+ title: 'First Post',
243
+ authorId: user.id,
244
+ });
245
+
246
+ // If any operation fails, all are rolled back
247
+ });
248
+
249
+ // Using query runner (more control)
250
+ const queryRunner = AppDataSource.createQueryRunner();
251
+ await queryRunner.connect();
252
+ await queryRunner.startTransaction();
253
+
254
+ try {
255
+ await queryRunner.manager.save(User, userData);
256
+ await queryRunner.manager.save(Post, postData);
257
+ await queryRunner.commitTransaction();
258
+ } catch (err) {
259
+ await queryRunner.rollbackTransaction();
260
+ throw err;
261
+ } finally {
262
+ await queryRunner.release();
263
+ }
264
+ \`\`\`
265
+
266
+ ## Custom Repositories
267
+ \`\`\`typescript
268
+ // Custom repository with DataSource
269
+ export const UserRepository = AppDataSource.getRepository(User).extend({
270
+ async findByEmail(email: string): Promise<User | null> {
271
+ return this.findOne({ where: { email } });
272
+ },
273
+
274
+ async findActiveWithPosts(): Promise<User[]> {
275
+ return this.find({
276
+ where: { deletedAt: IsNull() },
277
+ relations: ['posts'],
278
+ order: { createdAt: 'DESC' },
279
+ });
280
+ },
281
+
282
+ async softDeleteOldUsers(days: number): Promise<number> {
283
+ const date = new Date();
284
+ date.setDate(date.getDate() - days);
285
+
286
+ const result = await this.softDelete({
287
+ lastLoginAt: LessThan(date),
288
+ });
289
+
290
+ return result.affected || 0;
291
+ },
292
+ });
293
+
294
+ // Usage
295
+ const user = await UserRepository.findByEmail('test@example.com');
296
+ \`\`\`
297
+
298
+ ## Migrations
299
+ \`\`\`bash
300
+ # Generate migration from entity changes
301
+ npx typeorm migration:generate src/migrations/AddUserRole -d src/data-source.ts
302
+
303
+ # Create empty migration
304
+ npx typeorm migration:create src/migrations/SeedData
305
+
306
+ # Run migrations
307
+ npx typeorm migration:run -d src/data-source.ts
308
+
309
+ # Revert last migration
310
+ npx typeorm migration:revert -d src/data-source.ts
311
+ \`\`\`
312
+
313
+ \`\`\`typescript
314
+ // Migration file example
315
+ export class AddUserRole1234567890 implements MigrationInterface {
316
+ public async up(queryRunner: QueryRunner): Promise<void> {
317
+ await queryRunner.query(\`
318
+ ALTER TABLE "users" ADD "role" varchar DEFAULT 'user'
319
+ \`);
320
+ }
321
+
322
+ public async down(queryRunner: QueryRunner): Promise<void> {
323
+ await queryRunner.query(\`
324
+ ALTER TABLE "users" DROP COLUMN "role"
325
+ \`);
326
+ }
327
+ }
328
+ \`\`\`
329
+
330
+ ## Entity Subscribers
331
+ \`\`\`typescript
332
+ @EventSubscriber()
333
+ export class UserSubscriber implements EntitySubscriberInterface<User> {
334
+ listenTo() {
335
+ return User;
336
+ }
337
+
338
+ beforeInsert(event: InsertEvent<User>) {
339
+ console.log('Before insert:', event.entity);
340
+ }
341
+
342
+ afterInsert(event: InsertEvent<User>) {
343
+ // Send welcome email, log audit, etc.
344
+ }
345
+
346
+ afterUpdate(event: UpdateEvent<User>) {
347
+ if (event.updatedColumns.find(c => c.propertyName === 'email')) {
348
+ // Email changed, send verification
349
+ }
350
+ }
351
+ }
352
+ \`\`\`
353
+
354
+ ## ❌ DON'T
355
+ - Use \`synchronize: true\` in production
356
+ - Forget to handle N+1 queries (use relations or QueryBuilder joins)
357
+ - Use \`find()\` without limits on large tables
358
+ - Put business logic in entities
359
+ - Use raw queries without parameter binding
360
+
361
+ ## ✅ DO
362
+ - Use migrations for schema changes
363
+ - Use QueryBuilder for complex queries
364
+ - Use transactions for multi-step operations
365
+ - Use soft deletes for important data
366
+ - Use \`select: false\` for sensitive fields
367
+ - Use entity subscribers for side effects
368
+ - Create custom repository methods for reusable queries
@@ -0,0 +1,330 @@
1
+ # Vitest Skill
2
+
3
+ ## Test Structure
4
+ \`\`\`typescript
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+
7
+ describe('UserService', () => {
8
+ let service: UserService;
9
+ let mockDb: { findUser: ReturnType<typeof vi.fn> };
10
+
11
+ beforeEach(() => {
12
+ mockDb = { findUser: vi.fn() };
13
+ service = new UserService(mockDb);
14
+ });
15
+
16
+ afterEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ describe('getUser', () => {
21
+ it('should return user when found', async () => {
22
+ // Arrange
23
+ mockDb.findUser.mockResolvedValue({ id: '1', name: 'Test' });
24
+
25
+ // Act
26
+ const result = await service.getUser('1');
27
+
28
+ // Assert
29
+ expect(result).toEqual({ id: '1', name: 'Test' });
30
+ expect(mockDb.findUser).toHaveBeenCalledWith('1');
31
+ });
32
+
33
+ it('should throw when user not found', async () => {
34
+ mockDb.findUser.mockResolvedValue(null);
35
+
36
+ await expect(service.getUser('1')).rejects.toThrow('Not found');
37
+ });
38
+ });
39
+ });
40
+ \`\`\`
41
+
42
+ ## Mocking
43
+
44
+ ### Function Mocks
45
+ \`\`\`typescript
46
+ import { vi, expect } from 'vitest';
47
+
48
+ // Basic mock
49
+ const mockFn = vi.fn();
50
+ mockFn.mockReturnValue('default');
51
+ mockFn.mockReturnValueOnce('first call');
52
+
53
+ // Async mocks
54
+ const mockAsync = vi.fn();
55
+ mockAsync.mockResolvedValue({ data: 'success' });
56
+ mockAsync.mockRejectedValue(new Error('Failed'));
57
+
58
+ // Implementation mock
59
+ const mockImpl = vi.fn((x: number) => x * 2);
60
+
61
+ // Assertions
62
+ expect(mockFn).toHaveBeenCalled();
63
+ expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
64
+ expect(mockFn).toHaveBeenCalledTimes(2);
65
+ expect(mockFn).toHaveBeenLastCalledWith('lastArg');
66
+ expect(mockFn).toHaveReturnedWith('value');
67
+
68
+ // Reset mock
69
+ mockFn.mockClear(); // Clear call history
70
+ mockFn.mockReset(); // Clear history + implementation
71
+ mockFn.mockRestore(); // Restore original (for spies)
72
+ \`\`\`
73
+
74
+ ### Module Mocks
75
+ \`\`\`typescript
76
+ // Mock entire module (hoisted to top)
77
+ vi.mock('./userService', () => ({
78
+ UserService: vi.fn().mockImplementation(() => ({
79
+ getUser: vi.fn().mockResolvedValue({ id: '1' }),
80
+ })),
81
+ }));
82
+
83
+ // Mock with factory
84
+ vi.mock('./api', () => ({
85
+ fetchUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
86
+ fetchPosts: vi.fn(),
87
+ }));
88
+
89
+ // Partial mock (keep some real implementations)
90
+ vi.mock('./utils', async () => {
91
+ const actual = await vi.importActual('./utils');
92
+ return {
93
+ ...actual,
94
+ formatDate: vi.fn().mockReturnValue('2024-01-01'),
95
+ };
96
+ });
97
+
98
+ // Auto-mock all exports
99
+ vi.mock('./service'); // All exports become vi.fn()
100
+
101
+ // Get mocked module
102
+ import { fetchUser } from './api';
103
+ const mockedFetchUser = vi.mocked(fetchUser);
104
+ \`\`\`
105
+
106
+ ### Spy on Methods
107
+ \`\`\`typescript
108
+ // Spy on object method
109
+ const spy = vi.spyOn(console, 'log');
110
+ spy.mockImplementation(() => {}); // Suppress output
111
+
112
+ // Spy on prototype
113
+ const saveSpy = vi.spyOn(UserService.prototype, 'save');
114
+ saveSpy.mockResolvedValue({ id: '1' });
115
+
116
+ // Spy on getter/setter
117
+ vi.spyOn(object, 'property', 'get').mockReturnValue('mocked');
118
+
119
+ // Restore original
120
+ spy.mockRestore();
121
+ \`\`\`
122
+
123
+ ### Global Mocks
124
+ \`\`\`typescript
125
+ // Mock global function
126
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
127
+ ok: true,
128
+ json: () => Promise.resolve({ data: 'test' }),
129
+ }));
130
+
131
+ // Mock global object
132
+ vi.stubGlobal('localStorage', {
133
+ getItem: vi.fn(),
134
+ setItem: vi.fn(),
135
+ clear: vi.fn(),
136
+ });
137
+
138
+ // Mock environment variables
139
+ vi.stubEnv('API_URL', 'http://test.com');
140
+
141
+ // Unstub
142
+ vi.unstubAllGlobals();
143
+ vi.unstubAllEnvs();
144
+ \`\`\`
145
+
146
+ ## Timer Mocks
147
+ \`\`\`typescript
148
+ import { vi, beforeEach, afterEach, it, expect } from 'vitest';
149
+
150
+ beforeEach(() => {
151
+ vi.useFakeTimers();
152
+ });
153
+
154
+ afterEach(() => {
155
+ vi.useRealTimers();
156
+ });
157
+
158
+ it('should debounce calls', () => {
159
+ const callback = vi.fn();
160
+ const debounced = debounce(callback, 1000);
161
+
162
+ debounced();
163
+ debounced();
164
+ debounced();
165
+
166
+ expect(callback).not.toHaveBeenCalled();
167
+
168
+ vi.advanceTimersByTime(1000);
169
+
170
+ expect(callback).toHaveBeenCalledTimes(1);
171
+ });
172
+
173
+ it('should handle async timers', async () => {
174
+ const callback = vi.fn();
175
+ setTimeout(callback, 1000);
176
+
177
+ await vi.advanceTimersByTimeAsync(1000);
178
+ expect(callback).toHaveBeenCalled();
179
+ });
180
+
181
+ // Mock Date
182
+ vi.setSystemTime(new Date('2024-01-15'));
183
+ expect(new Date().toISOString()).toBe('2024-01-15T00:00:00.000Z');
184
+
185
+ // Run all timers
186
+ vi.runAllTimers();
187
+ vi.runOnlyPendingTimers(); // Avoid infinite loops with recursive timers
188
+ \`\`\`
189
+
190
+ ## Snapshot Testing
191
+ \`\`\`typescript
192
+ import { expect, it } from 'vitest';
193
+
194
+ // File snapshot
195
+ it('should match snapshot', () => {
196
+ const result = formatUser({ name: 'John', email: 'john@example.com' });
197
+ expect(result).toMatchSnapshot();
198
+ });
199
+
200
+ // Inline snapshot
201
+ it('should match inline snapshot', () => {
202
+ expect(formatUser(user)).toMatchInlineSnapshot(\`
203
+ {
204
+ "name": "John Doe",
205
+ "email": "john@example.com"
206
+ }
207
+ \`);
208
+ });
209
+
210
+ // Update snapshots: vitest --update or vitest -u
211
+ \`\`\`
212
+
213
+ ## Test Utilities
214
+ \`\`\`typescript
215
+ // Parameterized tests
216
+ import { describe, it, expect } from 'vitest';
217
+
218
+ describe.each([
219
+ { input: 1, expected: 2 },
220
+ { input: 2, expected: 4 },
221
+ { input: 3, expected: 6 },
222
+ ])('double($input)', ({ input, expected }) => {
223
+ it(\`should return \${expected}\`, () => {
224
+ expect(double(input)).toBe(expected);
225
+ });
226
+ });
227
+
228
+ // Skip and focus
229
+ describe.skip('skipped suite', () => {});
230
+ it.skip('skipped test', () => {});
231
+ describe.only('focused suite', () => {}); // Only run this
232
+ it.only('focused test', () => {}); // Only run this
233
+
234
+ // Todo tests
235
+ it.todo('should implement this feature');
236
+
237
+ // Concurrent tests (run in parallel)
238
+ describe.concurrent('parallel tests', () => {
239
+ it('test 1', async () => {});
240
+ it('test 2', async () => {});
241
+ });
242
+
243
+ // Retry flaky tests
244
+ it('flaky test', { retry: 3 }, async () => {});
245
+
246
+ // Timeout
247
+ it('slow test', { timeout: 10000 }, async () => {});
248
+ \`\`\`
249
+
250
+ ## Type Testing
251
+ \`\`\`typescript
252
+ import { expectTypeOf, assertType } from 'vitest';
253
+
254
+ it('should have correct types', () => {
255
+ // Check type equality
256
+ expectTypeOf(getUser).toBeFunction();
257
+ expectTypeOf(getUser).parameter(0).toBeString();
258
+ expectTypeOf(getUser).returns.resolves.toMatchTypeOf<User>();
259
+
260
+ // Check assignability
261
+ expectTypeOf<string>().toMatchTypeOf<string | number>();
262
+
263
+ // Assert type (compile-time only)
264
+ assertType<string>('hello');
265
+ });
266
+ \`\`\`
267
+
268
+ ## In-Source Testing
269
+ \`\`\`typescript
270
+ // src/utils.ts - tests in same file
271
+ export function add(a: number, b: number): number {
272
+ return a + b;
273
+ }
274
+
275
+ // Only included when running tests
276
+ if (import.meta.vitest) {
277
+ const { it, expect } = import.meta.vitest;
278
+
279
+ it('add', () => {
280
+ expect(add(1, 2)).toBe(3);
281
+ });
282
+ }
283
+ \`\`\`
284
+
285
+ ## Configuration (vitest.config.ts)
286
+ \`\`\`typescript
287
+ import { defineConfig } from 'vitest/config';
288
+
289
+ export default defineConfig({
290
+ test: {
291
+ globals: true, // Use describe, it, expect without imports
292
+ environment: 'node', // or 'jsdom', 'happy-dom'
293
+ include: ['**/*.{test,spec}.{ts,tsx}'],
294
+ exclude: ['node_modules', 'dist'],
295
+ coverage: {
296
+ provider: 'v8', // or 'istanbul'
297
+ reporter: ['text', 'html', 'lcov'],
298
+ exclude: ['**/*.d.ts', '**/*.test.ts'],
299
+ thresholds: {
300
+ branches: 80,
301
+ functions: 80,
302
+ lines: 80,
303
+ statements: 80,
304
+ },
305
+ },
306
+ setupFiles: ['./vitest.setup.ts'],
307
+ alias: {
308
+ '@': './src',
309
+ },
310
+ mockReset: true, // Reset mocks between tests
311
+ },
312
+ });
313
+ \`\`\`
314
+
315
+ ## ❌ DON'T
316
+ - Test implementation details
317
+ - Share state between tests
318
+ - Use \`.only\` in committed code
319
+ - Write flaky tests (timing-dependent)
320
+ - Forget to restore mocks
321
+ - Mock everything (test integrations too)
322
+
323
+ ## ✅ DO
324
+ - Use vi.mock for module mocking
325
+ - Clear mocks between tests (mockReset: true)
326
+ - Use vi.mocked() for type-safe mocked imports
327
+ - Use fake timers for time-dependent code
328
+ - Use concurrent tests for performance
329
+ - Leverage in-source testing for utilities
330
+ - Use type testing for public APIs