autoworkflow 3.1.5 → 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 +26 -0
  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,371 @@
1
+ # Testing Library Skill
2
+
3
+ ## Basic Test Structure
4
+ \`\`\`typescript
5
+ import { render, screen } from '@testing-library/react';
6
+ import userEvent from '@testing-library/user-event';
7
+ import { vi, expect, test, describe, beforeEach } from 'vitest';
8
+ import { LoginForm } from './LoginForm';
9
+
10
+ describe('LoginForm', () => {
11
+ const mockOnSubmit = vi.fn();
12
+
13
+ beforeEach(() => {
14
+ mockOnSubmit.mockClear();
15
+ });
16
+
17
+ test('submits form with valid data', async () => {
18
+ const user = userEvent.setup();
19
+
20
+ render(<LoginForm onSubmit={mockOnSubmit} />);
21
+
22
+ // Query by accessible role/label
23
+ await user.type(screen.getByLabelText('Email'), 'test@example.com');
24
+ await user.type(screen.getByLabelText('Password'), 'password123');
25
+ await user.click(screen.getByRole('button', { name: 'Sign in' }));
26
+
27
+ expect(mockOnSubmit).toHaveBeenCalledWith({
28
+ email: 'test@example.com',
29
+ password: 'password123',
30
+ });
31
+ });
32
+
33
+ test('shows validation error for invalid email', async () => {
34
+ const user = userEvent.setup();
35
+
36
+ render(<LoginForm onSubmit={mockOnSubmit} />);
37
+
38
+ await user.type(screen.getByLabelText('Email'), 'invalid-email');
39
+ await user.click(screen.getByRole('button', { name: 'Sign in' }));
40
+
41
+ expect(screen.getByRole('alert')).toHaveTextContent('Invalid email');
42
+ expect(mockOnSubmit).not.toHaveBeenCalled();
43
+ });
44
+ });
45
+ \`\`\`
46
+
47
+ ## Queries (Priority Order)
48
+
49
+ ### 1. Accessible Queries (Preferred)
50
+ \`\`\`typescript
51
+ // By role (best - tests accessibility)
52
+ screen.getByRole('button', { name: 'Submit' });
53
+ screen.getByRole('textbox', { name: 'Email' });
54
+ screen.getByRole('checkbox', { name: 'Remember me' });
55
+ screen.getByRole('link', { name: 'Sign up' });
56
+ screen.getByRole('heading', { name: 'Welcome', level: 1 });
57
+ screen.getByRole('list');
58
+ screen.getByRole('listitem');
59
+ screen.getByRole('dialog');
60
+ screen.getByRole('alert');
61
+ screen.getByRole('navigation');
62
+ screen.getByRole('combobox'); // Select/dropdown
63
+
64
+ // By label (form elements)
65
+ screen.getByLabelText('Email');
66
+ screen.getByLabelText(/email/i); // Regex
67
+
68
+ // By placeholder (when no label)
69
+ screen.getByPlaceholderText('Enter your email');
70
+
71
+ // By text (non-interactive elements)
72
+ screen.getByText('Welcome to our app');
73
+ screen.getByText(/welcome/i); // Case-insensitive
74
+ \`\`\`
75
+
76
+ ### 2. Semantic Queries
77
+ \`\`\`typescript
78
+ // By alt text (images)
79
+ screen.getByAltText('Company logo');
80
+
81
+ // By title attribute
82
+ screen.getByTitle('Close');
83
+
84
+ // By display value (inputs)
85
+ screen.getByDisplayValue('john@example.com');
86
+ \`\`\`
87
+
88
+ ### 3. Test IDs (Last Resort)
89
+ \`\`\`typescript
90
+ // When accessibility isn't possible
91
+ screen.getByTestId('custom-element');
92
+ // Requires: data-testid="custom-element" in JSX
93
+ \`\`\`
94
+
95
+ ## Query Variants
96
+ \`\`\`typescript
97
+ // getBy - throws if not found (single element)
98
+ screen.getByRole('button');
99
+
100
+ // queryBy - returns null if not found (for asserting absence)
101
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
102
+
103
+ // findBy - async, waits for element (returns Promise)
104
+ await screen.findByRole('alert');
105
+
106
+ // getAllBy - multiple elements (throws if none)
107
+ screen.getAllByRole('listitem');
108
+
109
+ // queryAllBy - multiple elements (empty array if none)
110
+ screen.queryAllByRole('listitem');
111
+
112
+ // findAllBy - async, multiple elements
113
+ await screen.findAllByRole('listitem');
114
+ \`\`\`
115
+
116
+ ## userEvent (Interactions)
117
+ \`\`\`typescript
118
+ import userEvent from '@testing-library/user-event';
119
+
120
+ // Always setup userEvent
121
+ const user = userEvent.setup();
122
+
123
+ // Click
124
+ await user.click(element);
125
+ await user.dblClick(element);
126
+ await user.tripleClick(element); // Select all text
127
+
128
+ // Type
129
+ await user.type(input, 'Hello World');
130
+ await user.type(input, 'Hello{Enter}'); // Special keys
131
+ await user.clear(input);
132
+
133
+ // Special keys: {Enter}, {Escape}, {Backspace}, {Delete}, {Tab}
134
+ // Modifiers: {Shift>}A{/Shift} (shift+A)
135
+
136
+ // Keyboard shortcuts
137
+ await user.keyboard('{Control>}a{/Control}'); // Ctrl+A
138
+ await user.keyboard('{Shift>}{Enter}{/Shift}'); // Shift+Enter
139
+
140
+ // Select options
141
+ await user.selectOptions(select, 'option-value');
142
+ await user.selectOptions(select, ['value1', 'value2']); // Multi-select
143
+
144
+ // Checkbox/Radio
145
+ await user.click(checkbox); // Toggle
146
+
147
+ // Hover
148
+ await user.hover(element);
149
+ await user.unhover(element);
150
+
151
+ // Tab navigation
152
+ await user.tab();
153
+ await user.tab({ shift: true }); // Shift+Tab
154
+
155
+ // Clipboard
156
+ await user.copy();
157
+ await user.cut();
158
+ await user.paste('pasted text');
159
+
160
+ // File upload
161
+ const file = new File(['content'], 'file.png', { type: 'image/png' });
162
+ await user.upload(fileInput, file);
163
+ await user.upload(fileInput, [file1, file2]);
164
+ \`\`\`
165
+
166
+ ## Async Utilities
167
+ \`\`\`typescript
168
+ import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
169
+
170
+ // findBy - built-in waiting
171
+ const alert = await screen.findByRole('alert');
172
+
173
+ // waitFor - custom condition
174
+ await waitFor(() => {
175
+ expect(screen.getByText('Loaded')).toBeInTheDocument();
176
+ });
177
+
178
+ // waitFor with options
179
+ await waitFor(
180
+ () => expect(mockFn).toHaveBeenCalled(),
181
+ { timeout: 5000, interval: 100 }
182
+ );
183
+
184
+ // Wait for element removal
185
+ await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));
186
+
187
+ // Or with findBy variant
188
+ await waitForElementToBeRemoved(screen.queryByText('Loading...'));
189
+ \`\`\`
190
+
191
+ ## Custom Matchers (jest-dom)
192
+ \`\`\`typescript
193
+ import '@testing-library/jest-dom';
194
+
195
+ // Visibility
196
+ expect(element).toBeVisible();
197
+ expect(element).not.toBeVisible();
198
+ expect(element).toBeInTheDocument();
199
+
200
+ // Form states
201
+ expect(input).toBeEnabled();
202
+ expect(input).toBeDisabled();
203
+ expect(input).toBeRequired();
204
+ expect(input).toBeValid();
205
+ expect(input).toBeInvalid();
206
+ expect(checkbox).toBeChecked();
207
+ expect(input).toHaveFocus();
208
+
209
+ // Content
210
+ expect(element).toHaveTextContent('Hello');
211
+ expect(element).toHaveTextContent(/hello/i);
212
+ expect(input).toHaveValue('test@example.com');
213
+ expect(input).toHaveDisplayValue('test@example.com');
214
+ expect(element).toBeEmptyDOMElement();
215
+
216
+ // Attributes
217
+ expect(element).toHaveAttribute('href', '/home');
218
+ expect(element).toHaveClass('btn-primary');
219
+ expect(element).toHaveStyle({ color: 'red' });
220
+
221
+ // Accessibility
222
+ expect(element).toHaveAccessibleName('Submit');
223
+ expect(element).toHaveAccessibleDescription('Submit the form');
224
+ \`\`\`
225
+
226
+ ## Testing with Context/Providers
227
+ \`\`\`typescript
228
+ import { render } from '@testing-library/react';
229
+ import { ThemeProvider } from './ThemeContext';
230
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
231
+
232
+ // Custom render with providers
233
+ function customRender(ui: React.ReactElement, options = {}) {
234
+ const queryClient = new QueryClient({
235
+ defaultOptions: { queries: { retry: false } },
236
+ });
237
+
238
+ function Wrapper({ children }: { children: React.ReactNode }) {
239
+ return (
240
+ <QueryClientProvider client={queryClient}>
241
+ <ThemeProvider>
242
+ {children}
243
+ </ThemeProvider>
244
+ </QueryClientProvider>
245
+ );
246
+ }
247
+
248
+ return render(ui, { wrapper: Wrapper, ...options });
249
+ }
250
+
251
+ // Usage
252
+ import { customRender as render } from '../test-utils';
253
+
254
+ test('renders with theme', () => {
255
+ render(<MyComponent />);
256
+ // Component has access to all providers
257
+ });
258
+ \`\`\`
259
+
260
+ ## Testing Hooks
261
+ \`\`\`typescript
262
+ import { renderHook, act } from '@testing-library/react';
263
+ import { useCounter } from './useCounter';
264
+
265
+ test('should increment counter', () => {
266
+ const { result } = renderHook(() => useCounter());
267
+
268
+ expect(result.current.count).toBe(0);
269
+
270
+ act(() => {
271
+ result.current.increment();
272
+ });
273
+
274
+ expect(result.current.count).toBe(1);
275
+ });
276
+
277
+ // With initial props
278
+ test('should start with initial value', () => {
279
+ const { result, rerender } = renderHook(
280
+ ({ initialCount }) => useCounter(initialCount),
281
+ { initialProps: { initialCount: 10 } }
282
+ );
283
+
284
+ expect(result.current.count).toBe(10);
285
+
286
+ // Re-render with new props
287
+ rerender({ initialCount: 20 });
288
+ });
289
+
290
+ // Async hooks
291
+ test('should fetch data', async () => {
292
+ const { result } = renderHook(() => useFetchUser('123'));
293
+
294
+ expect(result.current.loading).toBe(true);
295
+
296
+ await waitFor(() => {
297
+ expect(result.current.loading).toBe(false);
298
+ });
299
+
300
+ expect(result.current.data).toEqual({ id: '123', name: 'John' });
301
+ });
302
+ \`\`\`
303
+
304
+ ## Debugging
305
+ \`\`\`typescript
306
+ import { screen, prettyDOM } from '@testing-library/react';
307
+
308
+ // Print DOM to console
309
+ screen.debug(); // Entire document
310
+ screen.debug(element); // Specific element
311
+ screen.debug(element, 20000); // Increase max length
312
+
313
+ // Get pretty DOM string
314
+ console.log(prettyDOM(element));
315
+
316
+ // Log testing playground URL
317
+ screen.logTestingPlaygroundURL();
318
+
319
+ // Configure debug output
320
+ import { configure } from '@testing-library/react';
321
+ configure({ defaultHidden: true }); // Show hidden elements
322
+ \`\`\`
323
+
324
+ ## Setup File (vitest.setup.ts)
325
+ \`\`\`typescript
326
+ import '@testing-library/jest-dom/vitest';
327
+ import { cleanup } from '@testing-library/react';
328
+ import { afterEach, vi } from 'vitest';
329
+
330
+ // Cleanup after each test
331
+ afterEach(() => {
332
+ cleanup();
333
+ });
334
+
335
+ // Mock window.matchMedia
336
+ Object.defineProperty(window, 'matchMedia', {
337
+ writable: true,
338
+ value: vi.fn().mockImplementation((query) => ({
339
+ matches: false,
340
+ media: query,
341
+ onchange: null,
342
+ addEventListener: vi.fn(),
343
+ removeEventListener: vi.fn(),
344
+ })),
345
+ });
346
+
347
+ // Mock IntersectionObserver
348
+ class MockIntersectionObserver {
349
+ observe = vi.fn();
350
+ disconnect = vi.fn();
351
+ unobserve = vi.fn();
352
+ }
353
+ window.IntersectionObserver = MockIntersectionObserver as any;
354
+ \`\`\`
355
+
356
+ ## ❌ DON'T
357
+ - Use \`getByTestId\` when accessible queries work
358
+ - Use \`fireEvent\` when \`userEvent\` works
359
+ - Test implementation details (state, props, methods)
360
+ - Test third-party library internals
361
+ - Use arbitrary waits (\`setTimeout\`)
362
+ - Query by class or ID
363
+
364
+ ## ✅ DO
365
+ - Query by role, label, text (accessibility first)
366
+ - Use \`userEvent\` for interactions
367
+ - Test from user's perspective
368
+ - Use \`findBy\` for async elements
369
+ - Use \`queryBy\` to assert absence
370
+ - Create custom render with providers
371
+ - Use \`screen\` for queries (not destructured render)