blacksmith-cli 0.1.4 → 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 +276 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/backend/utils/__init__.py.hbs +0 -0
- package/src/templates/backend/utils/models.py.hbs +11 -0
- package/src/templates/frontend/package.json.hbs +9 -1
- package/src/templates/frontend/src/__tests__/setup.ts.hbs +21 -0
- package/src/templates/frontend/src/__tests__/test-utils.tsx.hbs +80 -0
- package/src/templates/frontend/src/pages/home/home.tsx.hbs +93 -11
- package/src/templates/frontend/tsconfig.app.json.hbs +1 -0
- package/src/templates/frontend/vite.config.ts.hbs +8 -0
- package/src/templates/resource/api-hooks/index.ts.hbs +2 -0
- package/src/templates/resource/{frontend/hooks → api-hooks}/use-{{kebabs}}-query.ts.hbs +10 -2
- package/src/templates/resource/{frontend/hooks → api-hooks}/use-{{kebab}}-mutations.ts.hbs +1 -1
- package/src/templates/resource/backend/models.py.hbs +2 -3
- package/src/templates/resource/frontend/components/{{kebab}}-card.tsx.hbs +1 -1
- package/src/templates/resource/frontend/components/{{kebab}}-list.tsx.hbs +1 -1
- package/src/templates/resource/frontend/index.ts.hbs +1 -2
- package/src/templates/resource/frontend/pages/{{kebabs}}-page.tsx.hbs +1 -1
- package/src/templates/resource/frontend/pages/{{kebab}}-detail-page.tsx.hbs +3 -11
- package/src/templates/resource/pages/components/{{kebab}}-card.tsx.hbs +1 -1
- package/src/templates/resource/pages/components/{{kebab}}-list.tsx.hbs +1 -1
- package/src/templates/resource/pages/hooks/index.ts.hbs +9 -0
- package/src/templates/resource/pages/index.ts.hbs +1 -2
- package/src/templates/resource/pages/{{kebabs}}-page.tsx.hbs +1 -1
- package/src/templates/resource/pages/{{kebab}}-detail-page.tsx.hbs +3 -11
- package/src/templates/frontend/src/pages/home/components/features-grid.tsx.hbs +0 -88
- package/src/templates/frontend/src/pages/home/components/getting-started.tsx.hbs +0 -88
- package/src/templates/frontend/src/pages/home/components/hero-section.tsx.hbs +0 -47
- package/src/templates/frontend/src/pages/home/components/resources-section.tsx.hbs +0 -34
- package/src/templates/resource/pages/hooks/use-{{kebabs}}-query.ts.hbs +0 -35
- package/src/templates/resource/pages/hooks/use-{{kebab}}-mutations.ts.hbs +0 -39
package/dist/index.js
CHANGED
|
@@ -311,7 +311,7 @@ pages/<page>/
|
|
|
311
311
|
\u251C\u2500\u2500 routes.tsx # RouteObject[] using Path enum
|
|
312
312
|
\u251C\u2500\u2500 index.ts # Re-exports public API
|
|
313
313
|
\u251C\u2500\u2500 components/ # Child components
|
|
314
|
-
\u2514\u2500\u2500 hooks/ #
|
|
314
|
+
\u2514\u2500\u2500 hooks/ # Page-local hooks (UI logic, not API hooks)
|
|
315
315
|
\`\`\`
|
|
316
316
|
- See the \`page-structure\` skill for full conventions
|
|
317
317
|
`;
|
|
@@ -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
|
-
-
|
|
1375
|
-
-
|
|
1376
|
-
-
|
|
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
|
};
|
|
@@ -1418,12 +1419,12 @@ const { data, errorMessage } = useApiQuery({
|
|
|
1418
1419
|
import { postsRetrieveOptions } from '@/api/generated/@tanstack/react-query.gen'
|
|
1419
1420
|
|
|
1420
1421
|
const { data: post, isLoading, errorMessage } = useApiQuery({
|
|
1421
|
-
...postsRetrieveOptions({ path: { id:
|
|
1422
|
+
...postsRetrieveOptions({ path: { id: id! } }),
|
|
1422
1423
|
})
|
|
1423
1424
|
|
|
1424
1425
|
// Conditional query (skip until id is available)
|
|
1425
1426
|
const { data } = useApiQuery({
|
|
1426
|
-
...postsRetrieveOptions({ path: { id:
|
|
1427
|
+
...postsRetrieveOptions({ path: { id: id! } }),
|
|
1427
1428
|
enabled: !!id,
|
|
1428
1429
|
})
|
|
1429
1430
|
\`\`\`
|
|
@@ -1478,7 +1479,7 @@ const deletePost = useApiMutation({
|
|
|
1478
1479
|
})
|
|
1479
1480
|
|
|
1480
1481
|
const handleDelete = async () => {
|
|
1481
|
-
await deletePost.mutateAsync({ path: { id:
|
|
1482
|
+
await deletePost.mutateAsync({ path: { id: id! } })
|
|
1482
1483
|
navigate('/posts')
|
|
1483
1484
|
}
|
|
1484
1485
|
\`\`\`
|
|
@@ -1573,7 +1574,7 @@ export function useCreatePost() {
|
|
|
1573
1574
|
})
|
|
1574
1575
|
}
|
|
1575
1576
|
|
|
1576
|
-
export function useUpdatePost(id:
|
|
1577
|
+
export function useUpdatePost(id: string) {
|
|
1577
1578
|
return useApiMutation({
|
|
1578
1579
|
...postsUpdateMutation(),
|
|
1579
1580
|
invalidateKeys: [
|
|
@@ -1624,7 +1625,7 @@ pages/<page>/
|
|
|
1624
1625
|
\u251C\u2500\u2500 routes.tsx # Exports RouteObject[] for this page
|
|
1625
1626
|
\u251C\u2500\u2500 index.ts # Re-exports public members (routes)
|
|
1626
1627
|
\u251C\u2500\u2500 components/ # Components private to this page (optional)
|
|
1627
|
-
\u2514\u2500\u2500 hooks/ #
|
|
1628
|
+
\u2514\u2500\u2500 hooks/ # Page-local hooks (UI logic, not API hooks)
|
|
1628
1629
|
\`\`\`
|
|
1629
1630
|
|
|
1630
1631
|
**\`routes.tsx\`** \u2014 defines the route config using the \`Path\` enum:
|
|
@@ -1678,8 +1679,7 @@ export const postsRoutes: RouteObject[] = [
|
|
|
1678
1679
|
**\`index.ts\`** \u2014 exports routes first:
|
|
1679
1680
|
\`\`\`ts
|
|
1680
1681
|
export { postsRoutes } from './routes'
|
|
1681
|
-
export { usePosts } from '
|
|
1682
|
-
export { useCreatePost, useUpdatePost, useDeletePost } from './hooks/use-post-mutations'
|
|
1682
|
+
export { usePosts, useCreatePost, useUpdatePost, useDeletePost } from '@/api/hooks/posts'
|
|
1683
1683
|
\`\`\`
|
|
1684
1684
|
|
|
1685
1685
|
### Route Paths (\`src/router/paths.ts\`)
|
|
@@ -3152,7 +3152,7 @@ function useOrdersPage() {
|
|
|
3152
3152
|
orders: data?.results ?? [],
|
|
3153
3153
|
pagination: { ...pagination, total: data?.count ?? 0 },
|
|
3154
3154
|
search,
|
|
3155
|
-
deleteOrder: (id:
|
|
3155
|
+
deleteOrder: (id: string) => deleteOrder.mutate({ path: { id } }),
|
|
3156
3156
|
}
|
|
3157
3157
|
}
|
|
3158
3158
|
\`\`\`
|
|
@@ -3428,6 +3428,244 @@ class OrderService:
|
|
|
3428
3428
|
}
|
|
3429
3429
|
};
|
|
3430
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
|
+
|
|
3431
3669
|
// src/skills/clean-code.ts
|
|
3432
3670
|
var cleanCodeSkill = {
|
|
3433
3671
|
id: "clean-code",
|
|
@@ -3564,14 +3802,16 @@ var aiGuidelinesSkill = {
|
|
|
3564
3802
|
|
|
3565
3803
|
### Checklist Before Finishing a Task
|
|
3566
3804
|
1. Backend tests pass: \`cd backend && ./venv/bin/python manage.py test\`
|
|
3567
|
-
2. Frontend
|
|
3568
|
-
3.
|
|
3569
|
-
4.
|
|
3570
|
-
5.
|
|
3571
|
-
6.
|
|
3572
|
-
7.
|
|
3573
|
-
8.
|
|
3574
|
-
9.
|
|
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)
|
|
3575
3815
|
`;
|
|
3576
3816
|
}
|
|
3577
3817
|
};
|
|
@@ -3598,6 +3838,7 @@ async function setupAiDev({ projectDir, projectName, includeBlacksmithUiSkill })
|
|
|
3598
3838
|
skills.push(uiDesignSkill);
|
|
3599
3839
|
}
|
|
3600
3840
|
skills.push(blacksmithCliSkill);
|
|
3841
|
+
skills.push(frontendTestingSkill);
|
|
3601
3842
|
skills.push(programmingParadigmsSkill);
|
|
3602
3843
|
skills.push(cleanCodeSkill);
|
|
3603
3844
|
skills.push(aiGuidelinesSkill);
|
|
@@ -4157,6 +4398,20 @@ async function makeResource(name) {
|
|
|
4157
4398
|
} catch {
|
|
4158
4399
|
syncSpinner.warn('Could not sync OpenAPI. Run "blacksmith sync" manually.');
|
|
4159
4400
|
}
|
|
4401
|
+
const apiHooksDir = path7.join(frontendDir, "src", "api", "hooks", names.kebabs);
|
|
4402
|
+
const apiHooksSpinner = spinner(`Creating API hooks: api/hooks/${names.kebabs}/`);
|
|
4403
|
+
try {
|
|
4404
|
+
renderDirectory(
|
|
4405
|
+
path7.join(templatesDir, "resource", "api-hooks"),
|
|
4406
|
+
apiHooksDir,
|
|
4407
|
+
context
|
|
4408
|
+
);
|
|
4409
|
+
apiHooksSpinner.succeed(`Created frontend/src/api/hooks/${names.kebabs}/`);
|
|
4410
|
+
} catch (error) {
|
|
4411
|
+
apiHooksSpinner.fail("Failed to create API hooks");
|
|
4412
|
+
log.error(error.message);
|
|
4413
|
+
process.exit(1);
|
|
4414
|
+
}
|
|
4160
4415
|
const frontendSpinner = spinner(`Creating frontend page: pages/${names.kebabs}/`);
|
|
4161
4416
|
try {
|
|
4162
4417
|
renderDirectory(
|
|
@@ -4286,6 +4541,7 @@ var allSkills = [
|
|
|
4286
4541
|
blacksmithUiAuthSkill,
|
|
4287
4542
|
blacksmithHooksSkill,
|
|
4288
4543
|
blacksmithCliSkill,
|
|
4544
|
+
frontendTestingSkill,
|
|
4289
4545
|
cleanCodeSkill,
|
|
4290
4546
|
aiGuidelinesSkill
|
|
4291
4547
|
];
|