red64-cli 0.1.0 → 0.3.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 (125) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/parseArgs.d.ts.map +1 -1
  3. package/dist/cli/parseArgs.js +5 -0
  4. package/dist/cli/parseArgs.js.map +1 -1
  5. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  6. package/dist/components/init/CompleteStep.js +2 -2
  7. package/dist/components/init/CompleteStep.js.map +1 -1
  8. package/dist/components/init/TestCheckStep.d.ts +16 -0
  9. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  10. package/dist/components/init/TestCheckStep.js +120 -0
  11. package/dist/components/init/TestCheckStep.js.map +1 -0
  12. package/dist/components/init/index.d.ts +1 -0
  13. package/dist/components/init/index.d.ts.map +1 -1
  14. package/dist/components/init/index.js +1 -0
  15. package/dist/components/init/index.js.map +1 -1
  16. package/dist/components/init/types.d.ts +9 -0
  17. package/dist/components/init/types.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  19. package/dist/components/screens/InitScreen.js +69 -6
  20. package/dist/components/screens/InitScreen.js.map +1 -1
  21. package/dist/components/screens/ListScreen.d.ts.map +1 -1
  22. package/dist/components/screens/ListScreen.js +28 -3
  23. package/dist/components/screens/ListScreen.js.map +1 -1
  24. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  25. package/dist/components/screens/StartScreen.js +212 -13
  26. package/dist/components/screens/StartScreen.js.map +1 -1
  27. package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
  28. package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
  29. package/dist/components/ui/ArtifactsSidebar.js +51 -0
  30. package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
  31. package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
  32. package/dist/components/ui/FeatureSidebar.js +1 -1
  33. package/dist/components/ui/FeatureSidebar.js.map +1 -1
  34. package/dist/components/ui/index.d.ts +1 -0
  35. package/dist/components/ui/index.d.ts.map +1 -1
  36. package/dist/components/ui/index.js +1 -0
  37. package/dist/components/ui/index.js.map +1 -1
  38. package/dist/services/ClaudeErrorDetector.js +3 -3
  39. package/dist/services/ClaudeErrorDetector.js.map +1 -1
  40. package/dist/services/ConfigService.d.ts +1 -0
  41. package/dist/services/ConfigService.d.ts.map +1 -1
  42. package/dist/services/ConfigService.js.map +1 -1
  43. package/dist/services/ProjectDetector.d.ts +28 -0
  44. package/dist/services/ProjectDetector.d.ts.map +1 -0
  45. package/dist/services/ProjectDetector.js +236 -0
  46. package/dist/services/ProjectDetector.js.map +1 -0
  47. package/dist/services/TestRunner.d.ts +46 -0
  48. package/dist/services/TestRunner.d.ts.map +1 -0
  49. package/dist/services/TestRunner.js +85 -0
  50. package/dist/services/TestRunner.js.map +1 -0
  51. package/dist/services/index.d.ts +2 -0
  52. package/dist/services/index.d.ts.map +1 -1
  53. package/dist/services/index.js +2 -0
  54. package/dist/services/index.js.map +1 -1
  55. package/dist/types/index.d.ts +13 -0
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/index.js.map +1 -1
  58. package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
  59. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  60. package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
  61. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  62. package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
  63. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  64. package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
  65. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  66. package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
  67. package/framework/stacks/generic/feedback.md +80 -0
  68. package/framework/stacks/nextjs/accessibility.md +437 -0
  69. package/framework/stacks/nextjs/api.md +431 -0
  70. package/framework/stacks/nextjs/coding-style.md +282 -0
  71. package/framework/stacks/nextjs/commenting.md +226 -0
  72. package/framework/stacks/nextjs/components.md +411 -0
  73. package/framework/stacks/nextjs/conventions.md +333 -0
  74. package/framework/stacks/nextjs/css.md +310 -0
  75. package/framework/stacks/nextjs/error-handling.md +442 -0
  76. package/framework/stacks/nextjs/feedback.md +124 -0
  77. package/framework/stacks/nextjs/migrations.md +332 -0
  78. package/framework/stacks/nextjs/models.md +362 -0
  79. package/framework/stacks/nextjs/queries.md +410 -0
  80. package/framework/stacks/nextjs/responsive.md +338 -0
  81. package/framework/stacks/nextjs/tech-stack.md +177 -0
  82. package/framework/stacks/nextjs/test-writing.md +475 -0
  83. package/framework/stacks/nextjs/validation.md +467 -0
  84. package/framework/stacks/python/api.md +468 -0
  85. package/framework/stacks/python/authentication.md +342 -0
  86. package/framework/stacks/python/code-quality.md +283 -0
  87. package/framework/stacks/python/code-refactoring.md +315 -0
  88. package/framework/stacks/python/coding-style.md +462 -0
  89. package/framework/stacks/python/conventions.md +399 -0
  90. package/framework/stacks/python/error-handling.md +512 -0
  91. package/framework/stacks/python/feedback.md +92 -0
  92. package/framework/stacks/python/implement-ai-llm.md +468 -0
  93. package/framework/stacks/python/migrations.md +388 -0
  94. package/framework/stacks/python/models.md +399 -0
  95. package/framework/stacks/python/python.md +232 -0
  96. package/framework/stacks/python/queries.md +451 -0
  97. package/framework/stacks/python/structure.md +245 -58
  98. package/framework/stacks/python/tech.md +92 -35
  99. package/framework/stacks/python/testing.md +380 -0
  100. package/framework/stacks/python/validation.md +471 -0
  101. package/framework/stacks/rails/authentication.md +176 -0
  102. package/framework/stacks/rails/code-quality.md +287 -0
  103. package/framework/stacks/rails/code-refactoring.md +299 -0
  104. package/framework/stacks/rails/feedback.md +130 -0
  105. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  106. package/framework/stacks/rails/rails.md +301 -0
  107. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  108. package/framework/stacks/rails/rails8-css.md +573 -0
  109. package/framework/stacks/rails/structure.md +140 -0
  110. package/framework/stacks/rails/tech.md +108 -0
  111. package/framework/stacks/react/code-quality.md +521 -0
  112. package/framework/stacks/react/components.md +625 -0
  113. package/framework/stacks/react/data-fetching.md +586 -0
  114. package/framework/stacks/react/feedback.md +110 -0
  115. package/framework/stacks/react/forms.md +694 -0
  116. package/framework/stacks/react/performance.md +640 -0
  117. package/framework/stacks/react/product.md +22 -9
  118. package/framework/stacks/react/state-management.md +472 -0
  119. package/framework/stacks/react/structure.md +351 -44
  120. package/framework/stacks/react/tech.md +219 -30
  121. package/framework/stacks/react/testing.md +690 -0
  122. package/package.json +1 -1
  123. package/framework/stacks/node/product.md +0 -27
  124. package/framework/stacks/node/structure.md +0 -82
  125. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,475 @@
1
+ # Testing Patterns
2
+
3
+ Comprehensive testing conventions for Next.js with Vitest, React Testing Library, Playwright, and MSW.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Fast feedback**: Unit tests run in milliseconds, no I/O
10
+ - **Test behavior, not implementation**: Assert what the user sees and does, not internal state
11
+ - **Minimal during development**: Focus on critical paths first; add edge cases in dedicated testing phases
12
+ - **Realistic integration**: Use MSW to mock APIs, not implementation details
13
+
14
+ ---
15
+
16
+ ## Test Organization
17
+
18
+ ```
19
+ src/
20
+ lib/
21
+ __tests__/
22
+ format-date.test.ts
23
+ user-service.test.ts
24
+ components/
25
+ __tests__/
26
+ user-card.test.tsx
27
+ login-form.test.tsx
28
+ actions/
29
+ __tests__/
30
+ user-actions.test.ts
31
+ tests/
32
+ e2e/
33
+ auth.spec.ts
34
+ dashboard.spec.ts
35
+ fixtures/
36
+ users.ts
37
+ helpers/
38
+ setup.ts
39
+ ```
40
+
41
+ **Pattern**: Colocate unit tests with source using `__tests__/` directories. Place E2E tests in the project root `tests/e2e/`.
42
+
43
+ ---
44
+
45
+ ## Vitest Configuration
46
+
47
+ ```typescript
48
+ // vitest.config.ts
49
+ import { defineConfig } from "vitest/config";
50
+ import react from "@vitejs/plugin-react";
51
+ import tsconfigPaths from "vite-tsconfig-paths";
52
+
53
+ export default defineConfig({
54
+ plugins: [react(), tsconfigPaths()],
55
+ test: {
56
+ environment: "jsdom",
57
+ globals: true,
58
+ setupFiles: ["./tests/helpers/setup.ts"],
59
+ include: ["src/**/*.test.{ts,tsx}"],
60
+ coverage: {
61
+ provider: "v8",
62
+ include: ["src/**"],
63
+ exclude: ["src/**/*.d.ts", "src/**/__tests__/**"],
64
+ },
65
+ },
66
+ });
67
+ ```
68
+
69
+ ### Setup File
70
+
71
+ ```typescript
72
+ // tests/helpers/setup.ts
73
+ import "@testing-library/jest-dom/vitest";
74
+ import { cleanup } from "@testing-library/react";
75
+ import { afterEach } from "vitest";
76
+
77
+ afterEach(() => {
78
+ cleanup();
79
+ });
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Unit Testing
85
+
86
+ ### Pure Functions
87
+
88
+ ```typescript
89
+ // lib/__tests__/format-date.test.ts
90
+ import { describe, it, expect } from "vitest";
91
+ import { formatRelativeDate } from "../format-date";
92
+
93
+ describe("formatRelativeDate", () => {
94
+ it("returns 'just now' for current time", () => {
95
+ expect(formatRelativeDate(new Date())).toBe("just now");
96
+ });
97
+
98
+ it("returns '2 hours ago' for two hours past", () => {
99
+ const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000);
100
+ expect(formatRelativeDate(twoHoursAgo)).toBe("2 hours ago");
101
+ });
102
+
103
+ it("returns absolute date for dates older than 7 days", () => {
104
+ const oldDate = new Date("2023-01-15");
105
+ expect(formatRelativeDate(oldDate)).toMatch(/Jan 15, 2023/);
106
+ });
107
+ });
108
+ ```
109
+
110
+ ### Service Functions
111
+
112
+ ```typescript
113
+ // lib/__tests__/user-service.test.ts
114
+ import { describe, it, expect, vi } from "vitest";
115
+ import { createUser } from "../services/user-service";
116
+
117
+ // Mock Prisma
118
+ vi.mock("@/lib/prisma", () => ({
119
+ prisma: {
120
+ user: {
121
+ findUnique: vi.fn(),
122
+ create: vi.fn(),
123
+ },
124
+ },
125
+ }));
126
+
127
+ import { prisma } from "@/lib/prisma";
128
+
129
+ describe("createUser", () => {
130
+ it("creates a user when email is not taken", async () => {
131
+ vi.mocked(prisma.user.findUnique).mockResolvedValue(null);
132
+ vi.mocked(prisma.user.create).mockResolvedValue({
133
+ id: "1",
134
+ email: "jane@example.com",
135
+ name: "Jane",
136
+ } as any);
137
+
138
+ const user = await createUser({ email: "jane@example.com", name: "Jane" });
139
+
140
+ expect(user.email).toBe("jane@example.com");
141
+ expect(prisma.user.create).toHaveBeenCalledWith({
142
+ data: { email: "jane@example.com", name: "Jane" },
143
+ });
144
+ });
145
+
146
+ it("throws ConflictError when email exists", async () => {
147
+ vi.mocked(prisma.user.findUnique).mockResolvedValue({ id: "1" } as any);
148
+
149
+ await expect(
150
+ createUser({ email: "taken@example.com", name: "Test" })
151
+ ).rejects.toThrow("already exists");
152
+ });
153
+ });
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Component Testing
159
+
160
+ ### React Testing Library
161
+
162
+ ```typescript
163
+ // components/__tests__/user-card.test.tsx
164
+ import { render, screen } from "@testing-library/react";
165
+ import { describe, it, expect } from "vitest";
166
+ import { UserCard } from "../user-card";
167
+
168
+ describe("UserCard", () => {
169
+ const user = {
170
+ id: "1",
171
+ name: "Jane Doe",
172
+ email: "jane@example.com",
173
+ avatarUrl: null,
174
+ };
175
+
176
+ it("renders user name and email", () => {
177
+ render(<UserCard user={user} />);
178
+
179
+ expect(screen.getByText("Jane Doe")).toBeInTheDocument();
180
+ expect(screen.getByText("jane@example.com")).toBeInTheDocument();
181
+ });
182
+
183
+ it("hides email when showEmail is false", () => {
184
+ render(<UserCard user={user} showEmail={false} />);
185
+
186
+ expect(screen.getByText("Jane Doe")).toBeInTheDocument();
187
+ expect(screen.queryByText("jane@example.com")).not.toBeInTheDocument();
188
+ });
189
+
190
+ it("renders initials when no avatar URL", () => {
191
+ render(<UserCard user={user} />);
192
+
193
+ expect(screen.getByText("JD")).toBeInTheDocument();
194
+ });
195
+ });
196
+ ```
197
+
198
+ ### Testing Interactions
199
+
200
+ ```typescript
201
+ // components/__tests__/login-form.test.tsx
202
+ import { render, screen } from "@testing-library/react";
203
+ import userEvent from "@testing-library/user-event";
204
+ import { describe, it, expect, vi } from "vitest";
205
+
206
+ describe("LoginForm", () => {
207
+ it("submits form with email and password", async () => {
208
+ const user = userEvent.setup();
209
+ const onSubmit = vi.fn();
210
+
211
+ render(<LoginForm onSubmit={onSubmit} />);
212
+
213
+ await user.type(screen.getByLabelText("Email"), "jane@example.com");
214
+ await user.type(screen.getByLabelText("Password"), "secret123");
215
+ await user.click(screen.getByRole("button", { name: "Sign in" }));
216
+
217
+ expect(onSubmit).toHaveBeenCalledWith({
218
+ email: "jane@example.com",
219
+ password: "secret123",
220
+ });
221
+ });
222
+
223
+ it("shows validation error for invalid email", async () => {
224
+ const user = userEvent.setup();
225
+
226
+ render(<LoginForm onSubmit={vi.fn()} />);
227
+
228
+ await user.type(screen.getByLabelText("Email"), "not-an-email");
229
+ await user.click(screen.getByRole("button", { name: "Sign in" }));
230
+
231
+ expect(screen.getByText("Invalid email address")).toBeInTheDocument();
232
+ });
233
+
234
+ it("disables submit button while pending", async () => {
235
+ const user = userEvent.setup();
236
+ const onSubmit = vi.fn(() => new Promise(() => {})); // Never resolves
237
+
238
+ render(<LoginForm onSubmit={onSubmit} />);
239
+
240
+ await user.type(screen.getByLabelText("Email"), "jane@example.com");
241
+ await user.type(screen.getByLabelText("Password"), "secret123");
242
+ await user.click(screen.getByRole("button", { name: "Sign in" }));
243
+
244
+ expect(screen.getByRole("button", { name: /signing in/i })).toBeDisabled();
245
+ });
246
+ });
247
+ ```
248
+
249
+ ---
250
+
251
+ ## API Mocking with MSW
252
+
253
+ ### Setup
254
+
255
+ ```typescript
256
+ // tests/helpers/mocks/handlers.ts
257
+ import { http, HttpResponse } from "msw";
258
+
259
+ export const handlers = [
260
+ http.get("/api/users", () => {
261
+ return HttpResponse.json({
262
+ items: [
263
+ { id: "1", name: "Jane", email: "jane@example.com" },
264
+ { id: "2", name: "John", email: "john@example.com" },
265
+ ],
266
+ total: 2,
267
+ page: 1,
268
+ limit: 20,
269
+ totalPages: 1,
270
+ });
271
+ }),
272
+
273
+ http.post("/api/users", async ({ request }) => {
274
+ const body = await request.json();
275
+ return HttpResponse.json(
276
+ { id: "3", ...body, createdAt: new Date().toISOString() },
277
+ { status: 201 }
278
+ );
279
+ }),
280
+ ];
281
+ ```
282
+
283
+ ```typescript
284
+ // tests/helpers/mocks/server.ts
285
+ import { setupServer } from "msw/node";
286
+ import { handlers } from "./handlers";
287
+
288
+ export const server = setupServer(...handlers);
289
+ ```
290
+
291
+ ```typescript
292
+ // tests/helpers/setup.ts (add MSW)
293
+ import { server } from "./mocks/server";
294
+
295
+ beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
296
+ afterEach(() => server.resetHandlers());
297
+ afterAll(() => server.close());
298
+ ```
299
+
300
+ ### Override Handlers in Tests
301
+
302
+ ```typescript
303
+ import { http, HttpResponse } from "msw";
304
+ import { server } from "../../tests/helpers/mocks/server";
305
+
306
+ it("shows error when API fails", async () => {
307
+ server.use(
308
+ http.get("/api/users", () => {
309
+ return HttpResponse.json(
310
+ { error: { code: "INTERNAL_ERROR", message: "Server error" } },
311
+ { status: 500 }
312
+ );
313
+ })
314
+ );
315
+
316
+ render(<UserList />);
317
+
318
+ await screen.findByText("Failed to load users");
319
+ });
320
+ ```
321
+
322
+ ---
323
+
324
+ ## E2E Testing with Playwright
325
+
326
+ ### Configuration
327
+
328
+ ```typescript
329
+ // playwright.config.ts
330
+ import { defineConfig, devices } from "@playwright/test";
331
+
332
+ export default defineConfig({
333
+ testDir: "./tests/e2e",
334
+ fullyParallel: true,
335
+ forbidOnly: !!process.env.CI,
336
+ retries: process.env.CI ? 2 : 0,
337
+ workers: process.env.CI ? 1 : undefined,
338
+ reporter: "html",
339
+ use: {
340
+ baseURL: "http://localhost:3000",
341
+ trace: "on-first-retry",
342
+ },
343
+ projects: [
344
+ { name: "chromium", use: { ...devices["Desktop Chrome"] } },
345
+ { name: "mobile", use: { ...devices["iPhone 14"] } },
346
+ ],
347
+ webServer: {
348
+ command: "pnpm dev",
349
+ url: "http://localhost:3000",
350
+ reuseExistingServer: !process.env.CI,
351
+ },
352
+ });
353
+ ```
354
+
355
+ ### E2E Test Pattern
356
+
357
+ ```typescript
358
+ // tests/e2e/auth.spec.ts
359
+ import { test, expect } from "@playwright/test";
360
+
361
+ test.describe("Authentication", () => {
362
+ test("user can sign in and see dashboard", async ({ page }) => {
363
+ await page.goto("/login");
364
+
365
+ await page.getByLabel("Email").fill("admin@example.com");
366
+ await page.getByLabel("Password").fill("password123");
367
+ await page.getByRole("button", { name: "Sign in" }).click();
368
+
369
+ await expect(page).toHaveURL("/dashboard");
370
+ await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
371
+ });
372
+
373
+ test("shows error for invalid credentials", async ({ page }) => {
374
+ await page.goto("/login");
375
+
376
+ await page.getByLabel("Email").fill("wrong@example.com");
377
+ await page.getByLabel("Password").fill("wrongpassword");
378
+ await page.getByRole("button", { name: "Sign in" }).click();
379
+
380
+ await expect(page.getByText("Invalid credentials")).toBeVisible();
381
+ await expect(page).toHaveURL("/login");
382
+ });
383
+ });
384
+ ```
385
+
386
+ ### Page Object Pattern
387
+
388
+ ```typescript
389
+ // tests/e2e/pages/login-page.ts
390
+ import { type Page, type Locator } from "@playwright/test";
391
+
392
+ export class LoginPage {
393
+ readonly emailInput: Locator;
394
+ readonly passwordInput: Locator;
395
+ readonly submitButton: Locator;
396
+
397
+ constructor(private page: Page) {
398
+ this.emailInput = page.getByLabel("Email");
399
+ this.passwordInput = page.getByLabel("Password");
400
+ this.submitButton = page.getByRole("button", { name: "Sign in" });
401
+ }
402
+
403
+ async goto() {
404
+ await this.page.goto("/login");
405
+ }
406
+
407
+ async login(email: string, password: string) {
408
+ await this.emailInput.fill(email);
409
+ await this.passwordInput.fill(password);
410
+ await this.submitButton.click();
411
+ }
412
+ }
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Test Fixtures
418
+
419
+ ```typescript
420
+ // tests/fixtures/users.ts
421
+ export const testUsers = {
422
+ admin: {
423
+ id: "user-admin",
424
+ email: "admin@example.com",
425
+ name: "Admin User",
426
+ role: "ADMIN" as const,
427
+ },
428
+ member: {
429
+ id: "user-member",
430
+ email: "member@example.com",
431
+ name: "Regular User",
432
+ role: "MEMBER" as const,
433
+ },
434
+ } as const;
435
+ ```
436
+
437
+ ---
438
+
439
+ ## Test Commands
440
+
441
+ ```bash
442
+ # Unit tests (fast feedback)
443
+ pnpm vitest run # Run once
444
+ pnpm vitest # Watch mode
445
+ pnpm vitest run src/lib/__tests__/ # Specific directory
446
+
447
+ # Coverage
448
+ pnpm vitest run --coverage
449
+
450
+ # E2E tests
451
+ pnpm playwright test # All browsers
452
+ pnpm playwright test --project=chromium # Single browser
453
+ pnpm playwright test tests/e2e/auth.spec.ts # Single file
454
+ pnpm playwright test --ui # Interactive UI mode
455
+
456
+ # CI pipeline
457
+ pnpm vitest run && pnpm playwright test
458
+ ```
459
+
460
+ ---
461
+
462
+ ## Anti-Patterns
463
+
464
+ | Anti-Pattern | Problem | Correct Approach |
465
+ |---|---|---|
466
+ | Testing implementation details | Brittle, breaks on refactor | Test behavior: what the user sees and does |
467
+ | Snapshot tests for everything | Meaningless diffs, rubber-stamp approvals | Use snapshots sparingly; prefer explicit assertions |
468
+ | No MSW for API calls | Tests depend on real API, flaky | Mock at the network level with MSW |
469
+ | Slow unit tests (> 100ms each) | Developers stop running them | Mock I/O, no database in unit tests |
470
+ | Testing third-party libraries | Wasted effort | Trust the library; test your integration |
471
+ | No E2E for critical paths | Bugs in user flows slip through | E2E test login, signup, and primary workflows |
472
+
473
+ ---
474
+
475
+ _Tests document behavior. Write them for the critical paths first, keep them fast, and assert what the user experiences._