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,400 @@
1
+ # Java Skill
2
+
3
+ ## Records (Java 16+)
4
+ \`\`\`java
5
+ // Immutable data carriers
6
+ public record User(
7
+ String id,
8
+ String email,
9
+ String name,
10
+ boolean isActive,
11
+ Instant createdAt
12
+ ) {
13
+ // Compact constructor for validation
14
+ public User {
15
+ Objects.requireNonNull(id, "id cannot be null");
16
+ Objects.requireNonNull(email, "email cannot be null");
17
+ if (!email.contains("@")) {
18
+ throw new IllegalArgumentException("Invalid email format");
19
+ }
20
+ }
21
+
22
+ // Additional methods
23
+ public String displayName() {
24
+ return name != null ? name : email.split("@")[0];
25
+ }
26
+ }
27
+
28
+ // DTOs as records
29
+ public record CreateUserRequest(
30
+ @NotBlank String email,
31
+ @NotBlank @Size(min = 2, max = 100) String name,
32
+ @NotBlank @Size(min = 8) String password
33
+ ) {}
34
+
35
+ public record UserResponse(
36
+ String id,
37
+ String email,
38
+ String name,
39
+ boolean isActive
40
+ ) {
41
+ public static UserResponse from(User user) {
42
+ return new UserResponse(
43
+ user.id(),
44
+ user.email(),
45
+ user.name(),
46
+ user.isActive()
47
+ );
48
+ }
49
+ }
50
+
51
+ public record PaginatedResponse<T>(
52
+ List<T> items,
53
+ long total,
54
+ int page,
55
+ int perPage
56
+ ) {
57
+ public int totalPages() {
58
+ return (int) Math.ceil((double) total / perPage);
59
+ }
60
+
61
+ public boolean hasNext() {
62
+ return page < totalPages();
63
+ }
64
+ }
65
+ \`\`\`
66
+
67
+ ## Optional Patterns
68
+ \`\`\`java
69
+ import java.util.Optional;
70
+
71
+ public class UserService {
72
+ private final UserRepository repository;
73
+
74
+ // Return Optional for queries that may not find a result
75
+ public Optional<User> findById(String id) {
76
+ return repository.findById(id);
77
+ }
78
+
79
+ // Use Optional methods instead of null checks
80
+ public User getUser(String id) {
81
+ return findById(id)
82
+ .orElseThrow(() -> new NotFoundException("User not found: " + id));
83
+ }
84
+
85
+ // Chaining Optional operations
86
+ public Optional<String> getUserEmail(String id) {
87
+ return findById(id)
88
+ .filter(User::isActive)
89
+ .map(User::email);
90
+ }
91
+
92
+ // Optional with default value
93
+ public String getUserName(String id) {
94
+ return findById(id)
95
+ .map(User::name)
96
+ .orElse("Anonymous");
97
+ }
98
+
99
+ // Optional with lazy default
100
+ public User getOrCreate(String email) {
101
+ return repository.findByEmail(email)
102
+ .orElseGet(() -> repository.save(new User(email)));
103
+ }
104
+
105
+ // Avoid Optional.get() - use orElseThrow instead
106
+ public void processUser(String id) {
107
+ findById(id).ifPresentOrElse(
108
+ this::process,
109
+ () -> log.warn("User not found: {}", id)
110
+ );
111
+ }
112
+
113
+ // Flatten nested Optionals
114
+ public Optional<String> getProfileBio(String userId) {
115
+ return findById(userId)
116
+ .flatMap(User::profile) // profile returns Optional<Profile>
117
+ .map(Profile::bio);
118
+ }
119
+ }
120
+ \`\`\`
121
+
122
+ ## Stream API
123
+ \`\`\`java
124
+ import java.util.stream.*;
125
+
126
+ public class UserAnalytics {
127
+
128
+ // Filter, map, collect
129
+ public List<String> getActiveUserEmails(List<User> users) {
130
+ return users.stream()
131
+ .filter(User::isActive)
132
+ .map(User::email)
133
+ .sorted()
134
+ .toList(); // Java 16+ (or .collect(Collectors.toList()))
135
+ }
136
+
137
+ // Grouping
138
+ public Map<String, List<User>> groupByDomain(List<User> users) {
139
+ return users.stream()
140
+ .collect(Collectors.groupingBy(
141
+ user -> user.email().split("@")[1]
142
+ ));
143
+ }
144
+
145
+ // Counting
146
+ public Map<Boolean, Long> countByActiveStatus(List<User> users) {
147
+ return users.stream()
148
+ .collect(Collectors.partitioningBy(
149
+ User::isActive,
150
+ Collectors.counting()
151
+ ));
152
+ }
153
+
154
+ // Reducing
155
+ public int totalPostCount(List<User> users) {
156
+ return users.stream()
157
+ .mapToInt(User::postCount)
158
+ .sum();
159
+ }
160
+
161
+ // FlatMap for nested collections
162
+ public List<Post> getAllPosts(List<User> users) {
163
+ return users.stream()
164
+ .flatMap(user -> user.posts().stream())
165
+ .sorted(Comparator.comparing(Post::createdAt).reversed())
166
+ .toList();
167
+ }
168
+
169
+ // Finding elements
170
+ public Optional<User> findFirstAdmin(List<User> users) {
171
+ return users.stream()
172
+ .filter(u -> u.role().equals("ADMIN"))
173
+ .findFirst();
174
+ }
175
+
176
+ // Checking conditions
177
+ public boolean allUsersActive(List<User> users) {
178
+ return users.stream().allMatch(User::isActive);
179
+ }
180
+
181
+ public boolean anyUserAdmin(List<User> users) {
182
+ return users.stream().anyMatch(u -> u.role().equals("ADMIN"));
183
+ }
184
+
185
+ // Parallel streams (use for CPU-intensive operations on large datasets)
186
+ public List<UserStats> computeStats(List<User> users) {
187
+ return users.parallelStream()
188
+ .map(this::computeUserStats)
189
+ .toList();
190
+ }
191
+
192
+ // Collectors.toMap
193
+ public Map<String, User> userById(List<User> users) {
194
+ return users.stream()
195
+ .collect(Collectors.toMap(
196
+ User::id,
197
+ Function.identity(),
198
+ (existing, replacement) -> existing // Handle duplicates
199
+ ));
200
+ }
201
+ }
202
+ \`\`\`
203
+
204
+ ## Exception Handling
205
+ \`\`\`java
206
+ // Custom exceptions
207
+ public class AppException extends RuntimeException {
208
+ private final ErrorCode code;
209
+
210
+ public AppException(ErrorCode code, String message) {
211
+ super(message);
212
+ this.code = code;
213
+ }
214
+
215
+ public AppException(ErrorCode code, String message, Throwable cause) {
216
+ super(message, cause);
217
+ this.code = code;
218
+ }
219
+
220
+ public ErrorCode getCode() {
221
+ return code;
222
+ }
223
+ }
224
+
225
+ public class NotFoundException extends AppException {
226
+ public NotFoundException(String message) {
227
+ super(ErrorCode.NOT_FOUND, message);
228
+ }
229
+ }
230
+
231
+ public class ValidationException extends AppException {
232
+ private final Map<String, String> errors;
233
+
234
+ public ValidationException(Map<String, String> errors) {
235
+ super(ErrorCode.VALIDATION_ERROR, "Validation failed");
236
+ this.errors = errors;
237
+ }
238
+
239
+ public Map<String, String> getErrors() {
240
+ return errors;
241
+ }
242
+ }
243
+
244
+ // Try-with-resources
245
+ public void processFile(Path path) {
246
+ try (var reader = Files.newBufferedReader(path);
247
+ var writer = Files.newBufferedWriter(outputPath)) {
248
+ // Resources automatically closed
249
+ reader.lines()
250
+ .map(this::transform)
251
+ .forEach(line -> {
252
+ try {
253
+ writer.write(line);
254
+ writer.newLine();
255
+ } catch (IOException e) {
256
+ throw new UncheckedIOException(e);
257
+ }
258
+ });
259
+ } catch (IOException e) {
260
+ throw new AppException(ErrorCode.IO_ERROR, "Failed to process file", e);
261
+ }
262
+ }
263
+
264
+ // Wrapping checked exceptions
265
+ public User parseUser(String json) {
266
+ try {
267
+ return objectMapper.readValue(json, User.class);
268
+ } catch (JsonProcessingException e) {
269
+ throw new AppException(ErrorCode.PARSE_ERROR, "Invalid JSON", e);
270
+ }
271
+ }
272
+ \`\`\`
273
+
274
+ ## Collections
275
+ \`\`\`java
276
+ import java.util.*;
277
+
278
+ // Immutable collections (Java 9+)
279
+ List<String> names = List.of("Alice", "Bob", "Charlie");
280
+ Set<Integer> numbers = Set.of(1, 2, 3);
281
+ Map<String, Integer> scores = Map.of("Alice", 100, "Bob", 85);
282
+
283
+ // Mutable when needed
284
+ List<String> mutableList = new ArrayList<>(names);
285
+ mutableList.add("David");
286
+
287
+ // Map operations
288
+ Map<String, User> userCache = new HashMap<>();
289
+
290
+ // computeIfAbsent - lazy initialization
291
+ User user = userCache.computeIfAbsent(userId, id -> repository.findById(id).orElse(null));
292
+
293
+ // getOrDefault
294
+ int count = countMap.getOrDefault(key, 0);
295
+
296
+ // merge - accumulating values
297
+ wordCounts.merge(word, 1, Integer::sum);
298
+
299
+ // Concurrent collections
300
+ Map<String, User> concurrentCache = new ConcurrentHashMap<>();
301
+ Queue<Task> taskQueue = new ConcurrentLinkedQueue<>();
302
+ \`\`\`
303
+
304
+ ## Testing with JUnit 5
305
+ \`\`\`java
306
+ import org.junit.jupiter.api.*;
307
+ import org.junit.jupiter.params.ParameterizedTest;
308
+ import org.junit.jupiter.params.provider.*;
309
+ import static org.assertj.core.api.Assertions.*;
310
+ import static org.mockito.Mockito.*;
311
+
312
+ class UserServiceTest {
313
+ private UserRepository repository;
314
+ private UserService service;
315
+
316
+ @BeforeEach
317
+ void setUp() {
318
+ repository = mock(UserRepository.class);
319
+ service = new UserService(repository);
320
+ }
321
+
322
+ @Test
323
+ @DisplayName("findById returns user when exists")
324
+ void findById_whenUserExists_returnsUser() {
325
+ var user = new User("1", "test@example.com", "Test", true, Instant.now());
326
+ when(repository.findById("1")).thenReturn(Optional.of(user));
327
+
328
+ var result = service.findById("1");
329
+
330
+ assertThat(result).isPresent();
331
+ assertThat(result.get().email()).isEqualTo("test@example.com");
332
+ }
333
+
334
+ @Test
335
+ @DisplayName("getUser throws NotFoundException when not exists")
336
+ void getUser_whenNotExists_throwsException() {
337
+ when(repository.findById("999")).thenReturn(Optional.empty());
338
+
339
+ assertThatThrownBy(() -> service.getUser("999"))
340
+ .isInstanceOf(NotFoundException.class)
341
+ .hasMessageContaining("999");
342
+ }
343
+
344
+ @ParameterizedTest
345
+ @ValueSource(strings = {"", " ", "invalid-email"})
346
+ @DisplayName("validateEmail rejects invalid emails")
347
+ void validateEmail_withInvalidEmails_returnsFalse(String email) {
348
+ assertThat(service.validateEmail(email)).isFalse();
349
+ }
350
+
351
+ @ParameterizedTest
352
+ @CsvSource({
353
+ "test@example.com, true",
354
+ "user@domain.org, true",
355
+ "invalid, false",
356
+ ", false"
357
+ })
358
+ void validateEmail_withVariousInputs(String email, boolean expected) {
359
+ assertThat(service.validateEmail(email)).isEqualTo(expected);
360
+ }
361
+
362
+ @Test
363
+ void createUser_savesAndReturnsUser() {
364
+ var request = new CreateUserRequest("test@example.com", "Test", "password123");
365
+ when(repository.save(any(User.class))).thenAnswer(inv -> inv.getArgument(0));
366
+
367
+ var result = service.createUser(request);
368
+
369
+ assertThat(result.email()).isEqualTo("test@example.com");
370
+ verify(repository).save(any(User.class));
371
+ }
372
+
373
+ @Nested
374
+ @DisplayName("when user is admin")
375
+ class WhenAdmin {
376
+ @Test
377
+ void canDeleteOtherUsers() {
378
+ // ...
379
+ }
380
+ }
381
+ }
382
+ \`\`\`
383
+
384
+ ## ✅ DO
385
+ - Use records for immutable data carriers (DTOs, value objects)
386
+ - Use Optional for return types that may have no value
387
+ - Use streams for collection transformations
388
+ - Use var for local variables with obvious types
389
+ - Use try-with-resources for AutoCloseable resources
390
+ - Prefer List.of(), Set.of(), Map.of() for immutable collections
391
+ - Use @DisplayName for readable test names
392
+
393
+ ## ❌ DON'T
394
+ - Don't use Optional.get() - use orElseThrow() instead
395
+ - Don't use Optional as field types or method parameters
396
+ - Don't use null when Optional is appropriate
397
+ - Don't mutate collections during stream operations
398
+ - Don't use raw types (List instead of List<String>)
399
+ - Don't catch Exception or Throwable generically
400
+ - Don't use parallel streams for I/O operations
@@ -0,0 +1,313 @@
1
+ # Jest Skill
2
+
3
+ ## Test Structure
4
+ \`\`\`typescript
5
+ describe('UserService', () => {
6
+ let service: UserService;
7
+ let mockDb: jest.Mocked<Database>;
8
+
9
+ // Setup before each test
10
+ beforeEach(() => {
11
+ mockDb = { findUser: jest.fn(), saveUser: jest.fn() } as any;
12
+ service = new UserService(mockDb);
13
+ });
14
+
15
+ // Cleanup after each test
16
+ afterEach(() => {
17
+ jest.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
+ expect(mockDb.findUser).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ it('should throw when user not found', async () => {
35
+ mockDb.findUser.mockResolvedValue(null);
36
+
37
+ await expect(service.getUser('1')).rejects.toThrow('Not found');
38
+ });
39
+ });
40
+ });
41
+ \`\`\`
42
+
43
+ ## Mocking
44
+
45
+ ### Function Mocks
46
+ \`\`\`typescript
47
+ // Basic mock
48
+ const mockFn = jest.fn();
49
+ mockFn.mockReturnValue('default');
50
+ mockFn.mockReturnValueOnce('first call');
51
+
52
+ // Async mock
53
+ const mockAsync = jest.fn();
54
+ mockAsync.mockResolvedValue({ data: 'success' });
55
+ mockAsync.mockRejectedValue(new Error('Failed'));
56
+
57
+ // Implementation mock
58
+ const mockImpl = jest.fn((x: number) => x * 2);
59
+
60
+ // Assertions
61
+ expect(mockFn).toHaveBeenCalled();
62
+ expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
63
+ expect(mockFn).toHaveBeenCalledTimes(2);
64
+ expect(mockFn).toHaveBeenLastCalledWith('lastArg');
65
+ expect(mockFn).toHaveReturnedWith('value');
66
+ \`\`\`
67
+
68
+ ### Module Mocks
69
+ \`\`\`typescript
70
+ // Mock entire module
71
+ jest.mock('./userService');
72
+ import { UserService } from './userService';
73
+ const MockedUserService = UserService as jest.MockedClass<typeof UserService>;
74
+
75
+ // Mock with implementation
76
+ jest.mock('./api', () => ({
77
+ fetchUser: jest.fn().mockResolvedValue({ id: '1', name: 'Test' }),
78
+ fetchPosts: jest.fn(),
79
+ }));
80
+
81
+ // Partial mock (keep some real implementations)
82
+ jest.mock('./utils', () => ({
83
+ ...jest.requireActual('./utils'),
84
+ formatDate: jest.fn().mockReturnValue('2024-01-01'),
85
+ }));
86
+
87
+ // Mock default export
88
+ jest.mock('./config', () => ({
89
+ __esModule: true,
90
+ default: { apiUrl: 'http://test.com' },
91
+ }));
92
+ \`\`\`
93
+
94
+ ### Spy on Methods
95
+ \`\`\`typescript
96
+ // Spy on object method
97
+ const spy = jest.spyOn(console, 'log');
98
+ spy.mockImplementation(() => {}); // Suppress output
99
+
100
+ // Spy on prototype
101
+ const saveSpy = jest.spyOn(UserService.prototype, 'save');
102
+ saveSpy.mockResolvedValue({ id: '1' });
103
+
104
+ // Restore original
105
+ spy.mockRestore();
106
+ \`\`\`
107
+
108
+ ## Async Testing
109
+ \`\`\`typescript
110
+ // Async/await
111
+ it('should fetch data', async () => {
112
+ const data = await fetchData();
113
+ expect(data).toEqual({ id: 1 });
114
+ });
115
+
116
+ // Promises
117
+ it('should resolve', () => {
118
+ return expect(fetchData()).resolves.toEqual({ id: 1 });
119
+ });
120
+
121
+ it('should reject', () => {
122
+ return expect(fetchData()).rejects.toThrow('Error');
123
+ });
124
+
125
+ // Callbacks (use done)
126
+ it('should callback', (done) => {
127
+ fetchWithCallback((err, data) => {
128
+ expect(err).toBeNull();
129
+ expect(data).toEqual({ id: 1 });
130
+ done();
131
+ });
132
+ });
133
+
134
+ // Wait for assertions
135
+ it('should eventually update', async () => {
136
+ const result = await waitFor(() => {
137
+ expect(element).toHaveTextContent('Updated');
138
+ });
139
+ });
140
+ \`\`\`
141
+
142
+ ## Timer Mocks
143
+ \`\`\`typescript
144
+ beforeEach(() => {
145
+ jest.useFakeTimers();
146
+ });
147
+
148
+ afterEach(() => {
149
+ jest.useRealTimers();
150
+ });
151
+
152
+ it('should debounce calls', () => {
153
+ const callback = jest.fn();
154
+ const debounced = debounce(callback, 1000);
155
+
156
+ debounced();
157
+ debounced();
158
+ debounced();
159
+
160
+ expect(callback).not.toHaveBeenCalled();
161
+
162
+ jest.advanceTimersByTime(1000);
163
+
164
+ expect(callback).toHaveBeenCalledTimes(1);
165
+ });
166
+
167
+ it('should handle setTimeout', () => {
168
+ const callback = jest.fn();
169
+ setTimeout(callback, 5000);
170
+
171
+ jest.runAllTimers(); // Run all pending timers
172
+ // Or: jest.advanceTimersByTime(5000);
173
+
174
+ expect(callback).toHaveBeenCalled();
175
+ });
176
+
177
+ // For Date mocking
178
+ jest.setSystemTime(new Date('2024-01-15'));
179
+ \`\`\`
180
+
181
+ ## Snapshot Testing
182
+ \`\`\`typescript
183
+ // Component snapshot
184
+ it('should match snapshot', () => {
185
+ const tree = renderer.create(<Button label="Click me" />).toJSON();
186
+ expect(tree).toMatchSnapshot();
187
+ });
188
+
189
+ // Inline snapshot
190
+ it('should match inline snapshot', () => {
191
+ expect(formatUser(user)).toMatchInlineSnapshot(\`
192
+ {
193
+ "name": "John Doe",
194
+ "email": "john@example.com"
195
+ }
196
+ \`);
197
+ });
198
+
199
+ // Update snapshots: jest --updateSnapshot or jest -u
200
+ \`\`\`
201
+
202
+ ## Test Utilities
203
+ \`\`\`typescript
204
+ // Test each (parameterized tests)
205
+ describe.each([
206
+ { input: 1, expected: 2 },
207
+ { input: 2, expected: 4 },
208
+ { input: 3, expected: 6 },
209
+ ])('double($input)', ({ input, expected }) => {
210
+ it(\`should return \${expected}\`, () => {
211
+ expect(double(input)).toBe(expected);
212
+ });
213
+ });
214
+
215
+ // Table syntax
216
+ it.each\`
217
+ a | b | expected
218
+ \${1} | \${2} | \${3}
219
+ \${2} | \${3} | \${5}
220
+ \`('add($a, $b) = $expected', ({ a, b, expected }) => {
221
+ expect(add(a, b)).toBe(expected);
222
+ });
223
+
224
+ // Skip and focus
225
+ describe.skip('skipped suite', () => {});
226
+ it.skip('skipped test', () => {});
227
+ describe.only('focused suite', () => {}); // Only run this
228
+ it.only('focused test', () => {}); // Only run this
229
+
230
+ // Conditional tests
231
+ const itif = (condition: boolean) => condition ? it : it.skip;
232
+ itif(process.env.CI)('only runs in CI', () => {});
233
+ \`\`\`
234
+
235
+ ## Common Matchers
236
+ \`\`\`typescript
237
+ // Equality
238
+ expect(value).toBe(exact); // Strict equality (===)
239
+ expect(value).toEqual(object); // Deep equality
240
+ expect(value).toStrictEqual(object); // Deep equality + type checking
241
+
242
+ // Truthiness
243
+ expect(value).toBeTruthy();
244
+ expect(value).toBeFalsy();
245
+ expect(value).toBeNull();
246
+ expect(value).toBeUndefined();
247
+ expect(value).toBeDefined();
248
+
249
+ // Numbers
250
+ expect(value).toBeGreaterThan(3);
251
+ expect(value).toBeGreaterThanOrEqual(3);
252
+ expect(value).toBeLessThan(5);
253
+ expect(value).toBeCloseTo(0.3, 5); // Floating point
254
+
255
+ // Strings
256
+ expect(value).toMatch(/pattern/);
257
+ expect(value).toContain('substring');
258
+
259
+ // Arrays/Iterables
260
+ expect(array).toContain(item);
261
+ expect(array).toContainEqual({ id: 1 });
262
+ expect(array).toHaveLength(3);
263
+
264
+ // Objects
265
+ expect(object).toHaveProperty('key');
266
+ expect(object).toHaveProperty('nested.key', 'value');
267
+ expect(object).toMatchObject({ partial: 'match' });
268
+
269
+ // Errors
270
+ expect(() => fn()).toThrow();
271
+ expect(() => fn()).toThrow(Error);
272
+ expect(() => fn()).toThrow('message');
273
+ expect(() => fn()).toThrow(/pattern/);
274
+
275
+ // Negation
276
+ expect(value).not.toBe(other);
277
+ \`\`\`
278
+
279
+ ## Configuration (jest.config.js)
280
+ \`\`\`javascript
281
+ module.exports = {
282
+ preset: 'ts-jest',
283
+ testEnvironment: 'node', // or 'jsdom' for browser
284
+ roots: ['<rootDir>/src'],
285
+ testMatch: ['**/*.test.ts', '**/*.spec.ts'],
286
+ collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
287
+ coverageThreshold: {
288
+ global: { branches: 80, functions: 80, lines: 80, statements: 80 },
289
+ },
290
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
291
+ moduleNameMapper: {
292
+ '^@/(.*)$': '<rootDir>/src/$1',
293
+ },
294
+ };
295
+ \`\`\`
296
+
297
+ ## ❌ DON'T
298
+ - Test implementation details
299
+ - Share state between tests
300
+ - Use \`.only\` in committed code
301
+ - Write flaky tests (timing-dependent)
302
+ - Mock everything (test real integrations too)
303
+ - Skip error case testing
304
+
305
+ ## ✅ DO
306
+ - Follow Arrange-Act-Assert pattern
307
+ - Use descriptive test names
308
+ - Test behavior and outcomes
309
+ - Clear mocks between tests
310
+ - Test edge cases and errors
311
+ - Keep tests independent and isolated
312
+ - Use \`describe\` blocks to organize related tests
313
+ - Aim for meaningful coverage, not 100%