blacksmith-cli 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1371,9 +1371,10 @@ var reactSkill = {
1371
1371
  - Display user-facing errors using the project's feedback components (Alert, Toast)
1372
1372
 
1373
1373
  ### Testing
1374
- - Run all tests: \`cd frontend && npm test\`
1375
- - Run a specific test: \`cd frontend && npm test -- --grep "test name"\`
1376
- - Test files live alongside the code they test (\`component.test.tsx\`)
1374
+ - See the \`frontend-testing\` skill for full conventions on test placement, utilities, mocking, and what to test
1375
+ - **Every code change must include corresponding tests** \u2014 see the \`frontend-testing\` skill for the complete rules
1376
+ - Tests use \`.spec.tsx\` / \`.spec.ts\` and live in \`__tests__/\` folders co-located with source code
1377
+ - Always use \`renderWithProviders\` from \`@/__tests__/test-utils\` \u2014 never import \`render\` from \`@testing-library/react\` directly
1377
1378
  `;
1378
1379
  }
1379
1380
  };
@@ -3427,6 +3428,244 @@ class OrderService:
3427
3428
  }
3428
3429
  };
3429
3430
 
3431
+ // src/skills/frontend-testing.ts
3432
+ var frontendTestingSkill = {
3433
+ id: "frontend-testing",
3434
+ name: "Frontend Testing Conventions",
3435
+ description: "Test infrastructure, file placement, test utilities, and rules for when and how to write frontend tests.",
3436
+ render(_ctx) {
3437
+ return `## Frontend Testing Conventions
3438
+
3439
+ ### Stack
3440
+ - **Vitest** \u2014 test runner (configured in \`vite.config.ts\`)
3441
+ - **jsdom** \u2014 browser environment
3442
+ - **React Testing Library** \u2014 component rendering and queries
3443
+ - **\`@testing-library/user-event\`** \u2014 user interaction simulation
3444
+ - **\`@testing-library/jest-dom\`** \u2014 DOM assertion matchers (e.g. \`toBeInTheDocument\`)
3445
+
3446
+ ### Running Tests
3447
+ - Run all tests: \`cd frontend && npm test\`
3448
+ - Watch mode: \`cd frontend && npm run test:watch\`
3449
+ - Run a specific file: \`cd frontend && npx vitest run src/pages/home/__tests__/home.spec.tsx\`
3450
+ - Coverage: \`cd frontend && npm run test:coverage\`
3451
+
3452
+ ### File Placement \u2014 Tests Live Next to the Code
3453
+
3454
+ > **RULE: Every test file goes in a \`__tests__/\` folder co-located with the code it tests. Never put tests in a top-level \`tests/\` directory.**
3455
+
3456
+ \`\`\`
3457
+ pages/customers/
3458
+ \u251C\u2500\u2500 customers-page.tsx
3459
+ \u251C\u2500\u2500 customer-detail-page.tsx
3460
+ \u251C\u2500\u2500 __tests__/ # Page integration tests
3461
+ \u2502 \u251C\u2500\u2500 customers-page.spec.tsx
3462
+ \u2502 \u2514\u2500\u2500 customer-detail-page.spec.tsx
3463
+ \u251C\u2500\u2500 components/
3464
+ \u2502 \u251C\u2500\u2500 customer-card.tsx
3465
+ \u2502 \u251C\u2500\u2500 customer-list.tsx
3466
+ \u2502 \u251C\u2500\u2500 customer-form.tsx
3467
+ \u2502 \u2514\u2500\u2500 __tests__/ # Component unit tests
3468
+ \u2502 \u251C\u2500\u2500 customer-card.spec.tsx
3469
+ \u2502 \u251C\u2500\u2500 customer-list.spec.tsx
3470
+ \u2502 \u2514\u2500\u2500 customer-form.spec.tsx
3471
+ \u2514\u2500\u2500 hooks/
3472
+ \u251C\u2500\u2500 use-customers-page.ts
3473
+ \u2514\u2500\u2500 __tests__/ # Hook tests
3474
+ \u2514\u2500\u2500 use-customers-page.spec.ts
3475
+ \`\`\`
3476
+
3477
+ The same pattern applies to \`features/\`, \`shared/\`, and \`router/\`:
3478
+ \`\`\`
3479
+ features/auth/
3480
+ \u251C\u2500\u2500 pages/
3481
+ \u2502 \u251C\u2500\u2500 login-page.tsx
3482
+ \u2502 \u2514\u2500\u2500 __tests__/
3483
+ \u2502 \u2514\u2500\u2500 login-page.spec.tsx
3484
+ \u251C\u2500\u2500 hooks/
3485
+ \u2502 \u2514\u2500\u2500 __tests__/
3486
+ shared/
3487
+ \u251C\u2500\u2500 components/
3488
+ \u2502 \u2514\u2500\u2500 __tests__/
3489
+ \u251C\u2500\u2500 hooks/
3490
+ \u2502 \u2514\u2500\u2500 __tests__/
3491
+ router/
3492
+ \u251C\u2500\u2500 __tests__/
3493
+ \u2502 \u251C\u2500\u2500 paths.spec.ts
3494
+ \u2502 \u2514\u2500\u2500 auth-guard.spec.tsx
3495
+ \`\`\`
3496
+
3497
+ ### Test File Naming
3498
+ - Use \`.spec.tsx\` for component/page tests (JSX)
3499
+ - Use \`.spec.ts\` for pure logic tests (hooks, utilities, no JSX)
3500
+ - Name matches the source file: \`customer-card.tsx\` \u2192 \`customer-card.spec.tsx\`
3501
+
3502
+ ### Always Use \`renderWithProviders\`
3503
+
3504
+ > **RULE: Never import \`render\` from \`@testing-library/react\` directly. Always use \`renderWithProviders\` from \`@/__tests__/test-utils\`.**
3505
+
3506
+ \`renderWithProviders\` wraps components with all app providers (ThemeProvider, QueryClientProvider, MemoryRouter) so tests match the real app environment.
3507
+
3508
+ \`\`\`tsx
3509
+ import { screen } from '@/__tests__/test-utils'
3510
+ import { renderWithProviders } from '@/__tests__/test-utils'
3511
+ import { MyComponent } from '../my-component'
3512
+
3513
+ describe('MyComponent', () => {
3514
+ it('renders correctly', () => {
3515
+ renderWithProviders(<MyComponent />)
3516
+ expect(screen.getByText('Hello')).toBeInTheDocument()
3517
+ })
3518
+ })
3519
+ \`\`\`
3520
+
3521
+ **Options:**
3522
+ \`\`\`tsx
3523
+ renderWithProviders(<MyComponent />, {
3524
+ routerEntries: ['/customers/1'], // Set initial route
3525
+ queryClient: customQueryClient, // Custom query client
3526
+ })
3527
+ \`\`\`
3528
+
3529
+ **User interactions:**
3530
+ \`\`\`tsx
3531
+ const { user } = renderWithProviders(<MyComponent />)
3532
+ await user.click(screen.getByRole('button', { name: 'Submit' }))
3533
+ await user.type(screen.getByLabelText('Email'), 'test@example.com')
3534
+ \`\`\`
3535
+
3536
+ ### When to Write Tests
3537
+
3538
+ > **RULE: Every code change that touches pages, components, hooks, or utilities must include corresponding test updates.**
3539
+
3540
+ | What changed | Test required |
3541
+ |---|---|
3542
+ | New page | Add \`__tests__/<page>.spec.tsx\` with integration test |
3543
+ | New component | Add \`__tests__/<component>.spec.tsx\` |
3544
+ | New hook (with logic) | Add \`__tests__/<hook>.spec.ts\` |
3545
+ | New utility function | Add \`__tests__/<util>.spec.ts\` |
3546
+ | Modified page/component | Update existing test or add new test cases |
3547
+ | Bug fix | Add regression test that would have caught the bug |
3548
+ | Deleted page/component | Delete corresponding test file |
3549
+
3550
+ ### What to Test
3551
+
3552
+ **Page integration tests** \u2014 test the page as a whole:
3553
+ - Renders correct heading/title
3554
+ - Loading state shows skeleton or spinner
3555
+ - Error state shows error message
3556
+ - Data renders correctly (mock the API hooks)
3557
+ - User interactions (navigation, form submission, delete confirmation)
3558
+
3559
+ **Component unit tests** \u2014 test the component in isolation:
3560
+ - Renders with required props
3561
+ - Handles optional props correctly (present vs absent)
3562
+ - Displays correct content based on props
3563
+ - User interactions trigger correct callbacks
3564
+ - Conditional rendering (empty state, loading state)
3565
+
3566
+ **Hook tests** \u2014 test custom hooks with logic:
3567
+ - Returns correct initial state
3568
+ - Transforms data correctly
3569
+ - Side effects fire as expected
3570
+
3571
+ **Utility/pure function tests** \u2014 test input/output:
3572
+ - Happy path
3573
+ - Edge cases (empty input, null, special characters)
3574
+ - Error cases
3575
+
3576
+ ### Mocking Patterns
3577
+
3578
+ **Mock hooks (for page tests):**
3579
+ \`\`\`tsx
3580
+ vi.mock('@/api/hooks/customers')
3581
+ vi.mock('@/features/auth/hooks/use-auth')
3582
+
3583
+ import { useCustomers } from '@/api/hooks/customers'
3584
+
3585
+ beforeEach(() => {
3586
+ vi.mocked(useCustomers).mockReturnValue({
3587
+ data: { customers: mockCustomers, total: 2 },
3588
+ isLoading: false,
3589
+ errorMessage: null,
3590
+ } as any)
3591
+ })
3592
+ \`\`\`
3593
+
3594
+ **Mock external UI libraries (for auth page tests):**
3595
+ \`\`\`tsx
3596
+ vi.mock('@blacksmith-ui/auth', () => ({
3597
+ LoginForm: ({ onSubmit, error, loading }: any) => (
3598
+ <form onSubmit={(e: any) => { e.preventDefault(); onSubmit({ email: 'test@test.com', password: 'pass' }) }}>
3599
+ {error && <div data-testid="error">{error.message}</div>}
3600
+ <button type="submit">Sign In</button>
3601
+ </form>
3602
+ ),
3603
+ }))
3604
+ \`\`\`
3605
+
3606
+ **Mock react-router-dom hooks (for detail pages):**
3607
+ \`\`\`tsx
3608
+ const mockNavigate = vi.fn()
3609
+ vi.mock('react-router-dom', async () => {
3610
+ const actual = await vi.importActual('react-router-dom')
3611
+ return { ...actual, useParams: () => ({ id: '1' }), useNavigate: () => mockNavigate }
3612
+ })
3613
+ \`\`\`
3614
+
3615
+ ### Test Structure
3616
+ \`\`\`tsx
3617
+ import { screen, waitFor } from '@/__tests__/test-utils'
3618
+ import { renderWithProviders } from '@/__tests__/test-utils'
3619
+
3620
+ // Mocks at the top, before imports of modules that use them
3621
+ vi.mock('@/api/hooks/customers')
3622
+
3623
+ import { useCustomers } from '@/api/hooks/customers'
3624
+ import CustomersPage from '../customers-page'
3625
+
3626
+ const mockCustomers = [
3627
+ { id: '1', title: 'Acme Corp', created_at: '2024-01-15T10:00:00Z' },
3628
+ ]
3629
+
3630
+ describe('CustomersPage', () => {
3631
+ beforeEach(() => {
3632
+ vi.mocked(useCustomers).mockReturnValue({ ... } as any)
3633
+ })
3634
+
3635
+ it('renders page heading', () => {
3636
+ renderWithProviders(<CustomersPage />)
3637
+ expect(screen.getByText('Customers')).toBeInTheDocument()
3638
+ })
3639
+
3640
+ it('shows error message when API fails', () => {
3641
+ vi.mocked(useCustomers).mockReturnValue({
3642
+ data: undefined,
3643
+ isLoading: false,
3644
+ errorMessage: 'Failed to load',
3645
+ } as any)
3646
+
3647
+ renderWithProviders(<CustomersPage />)
3648
+ expect(screen.getByText('Failed to load')).toBeInTheDocument()
3649
+ })
3650
+ })
3651
+ \`\`\`
3652
+
3653
+ ### Key Rules
3654
+
3655
+ 1. **Tests live next to code** \u2014 \`__tests__/\` folder alongside the source, not in a separate top-level directory
3656
+ 2. **Always use \`renderWithProviders\`** \u2014 never import render from \`@testing-library/react\` directly
3657
+ 3. **Every page gets an integration test** \u2014 at minimum: renders heading, handles loading, handles errors
3658
+ 4. **Every component gets a unit test** \u2014 at minimum: renders with required props, handles optional props
3659
+ 5. **Mock at the hook level** \u2014 mock \`useCustomers\`, not \`fetch\`. Mock \`useAuth\`, not the auth adapter
3660
+ 6. **Test behavior, not implementation** \u2014 query by role, text, or label, not by class names or internal state
3661
+ 7. **No test-only IDs unless necessary** \u2014 prefer \`getByRole\`, \`getByText\`, \`getByLabelText\` over \`getByTestId\`
3662
+ 8. **Keep tests focused** \u2014 each \`it()\` tests one behavior. Don't assert 10 things in one test
3663
+ 9. **Clean up mocks** \u2014 use \`beforeEach\` to reset mock return values so tests don't leak state
3664
+ 10. **Update tests when code changes** \u2014 if you modify a component, update its tests. If you delete a component, delete its tests
3665
+ `;
3666
+ }
3667
+ };
3668
+
3430
3669
  // src/skills/clean-code.ts
3431
3670
  var cleanCodeSkill = {
3432
3671
  id: "clean-code",
@@ -3563,14 +3802,16 @@ var aiGuidelinesSkill = {
3563
3802
 
3564
3803
  ### Checklist Before Finishing a Task
3565
3804
  1. Backend tests pass: \`cd backend && ./venv/bin/python manage.py test\`
3566
- 2. Frontend builds: \`cd frontend && npm run build\`
3567
- 3. API types are in sync: \`blacksmith sync\`
3568
- 4. No lint errors in modified files
3569
- 5. All UI uses \`@blacksmith-ui/react\` components \u2014 no raw \`<div>\` for layout, no raw \`<h1>\`-\`<h6>\` for text
3570
- 6. Pages are modular \u2014 page file is a thin orchestrator, sections are in \`components/\`, logic in \`hooks/\`
3571
- 7. Logic is in hooks \u2014 no \`useApiQuery\`, \`useApiMutation\`, \`useEffect\`, or multi-\`useState\` in component bodies
3572
- 8. No hardcoded route paths \u2014 all paths use the \`Path\` enum from \`@/router/paths\`
3573
- 9. New routes have a corresponding \`Path\` enum entry
3805
+ 2. Frontend tests pass: \`cd frontend && npm test\`
3806
+ 3. Frontend builds: \`cd frontend && npm run build\`
3807
+ 4. API types are in sync: \`blacksmith sync\`
3808
+ 5. No lint errors in modified files
3809
+ 6. All UI uses \`@blacksmith-ui/react\` components \u2014 no raw \`<div>\` for layout, no raw \`<h1>\`-\`<h6>\` for text
3810
+ 7. Pages are modular \u2014 page file is a thin orchestrator, sections are in \`components/\`, logic in \`hooks/\`
3811
+ 8. Logic is in hooks \u2014 no \`useApiQuery\`, \`useApiMutation\`, \`useEffect\`, or multi-\`useState\` in component bodies
3812
+ 9. No hardcoded route paths \u2014 all paths use the \`Path\` enum from \`@/router/paths\`
3813
+ 10. New routes have a corresponding \`Path\` enum entry
3814
+ 11. **Tests are co-located** \u2014 every new or modified page, component, or hook has a corresponding \`.spec.tsx\` / \`.spec.ts\` in a \`__tests__/\` folder next to the source file (see the \`frontend-testing\` skill)
3574
3815
  `;
3575
3816
  }
3576
3817
  };
@@ -3597,6 +3838,7 @@ async function setupAiDev({ projectDir, projectName, includeBlacksmithUiSkill })
3597
3838
  skills.push(uiDesignSkill);
3598
3839
  }
3599
3840
  skills.push(blacksmithCliSkill);
3841
+ skills.push(frontendTestingSkill);
3600
3842
  skills.push(programmingParadigmsSkill);
3601
3843
  skills.push(cleanCodeSkill);
3602
3844
  skills.push(aiGuidelinesSkill);
@@ -4299,6 +4541,7 @@ var allSkills = [
4299
4541
  blacksmithUiAuthSkill,
4300
4542
  blacksmithHooksSkill,
4301
4543
  blacksmithCliSkill,
4544
+ frontendTestingSkill,
4302
4545
  cleanCodeSkill,
4303
4546
  aiGuidelinesSkill
4304
4547
  ];