create-simone-app 0.1.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.
- package/README.md +70 -0
- package/bin/create-simone-app.ts +6 -0
- package/package.json +61 -0
- package/src/cli.ts +97 -0
- package/src/matrix.ts +39 -0
- package/src/options.ts +137 -0
- package/src/prompts.ts +108 -0
- package/src/scaffold.ts +304 -0
- package/src/templates.ts +77 -0
- package/templates/api/base/.agents/skills/ai-rules/SKILL.md +12 -0
- package/templates/api/base/.agents/skills/plan-feature/SKILL.md +13 -0
- package/templates/api/base/.agents/skills/review-diff/SKILL.md +12 -0
- package/templates/api/base/.agents/skills/tdd-step/SKILL.md +14 -0
- package/templates/api/base/.claude/agents/reviewer.md +20 -0
- package/templates/api/base/.claude/hooks/format.sh +22 -0
- package/templates/api/base/.claude/settings.json +38 -0
- package/templates/api/base/.codex/agents/reviewer.toml +7 -0
- package/templates/api/base/.codex/config.toml +10 -0
- package/templates/api/base/.codex/hooks.json +12 -0
- package/templates/api/base/.env.example +2 -0
- package/templates/api/base/.github/workflows/ci.yml +18 -0
- package/templates/api/base/.prettierignore +10 -0
- package/templates/api/base/.prettierrc +20 -0
- package/templates/api/base/CLAUDE.md +1 -0
- package/templates/api/base/README.md +43 -0
- package/templates/api/base/TASK.md.template +30 -0
- package/templates/api/base/_gitignore +8 -0
- package/templates/api/base/docs/agents/base.md +54 -0
- package/templates/api/base/eslint.config.js +21 -0
- package/templates/api/base/openapi.yaml +233 -0
- package/templates/api/base/package.json +41 -0
- package/templates/api/base/scripts/agents-build.ts +26 -0
- package/templates/api/base/scripts/rules-check.ts +48 -0
- package/templates/api/base/scripts/rules-sync.ts +35 -0
- package/templates/api/base/scripts/rules.config.ts +28 -0
- package/templates/api/base/scripts/seed-regen.ts +64 -0
- package/templates/api/base/src/data/orders.json +187 -0
- package/templates/api/base/src/data/products.json +142 -0
- package/templates/api/base/src/data/repository.ts +55 -0
- package/templates/api/base/src/data/schemas.ts +44 -0
- package/templates/api/base/src/data/users.json +352 -0
- package/templates/api/base/src/lib/appName.ts +2 -0
- package/templates/api/base/src/lib/http-error.ts +14 -0
- package/templates/api/base/src/lib/pagination.ts +55 -0
- package/templates/api/base/src/modules/orders/orders.repository.ts +26 -0
- package/templates/api/base/src/modules/orders/orders.schema.ts +11 -0
- package/templates/api/base/src/modules/products/products.repository.ts +17 -0
- package/templates/api/base/src/modules/products/products.schema.ts +21 -0
- package/templates/api/base/src/modules/stats/stats.repository.ts +37 -0
- package/templates/api/base/src/modules/users/users.repository.ts +26 -0
- package/templates/api/base/src/modules/users/users.schema.ts +15 -0
- package/templates/api/base/test/docs.test.ts +23 -0
- package/templates/api/base/test/health.test.ts +19 -0
- package/templates/api/base/test/orders.test.ts +82 -0
- package/templates/api/base/test/products.test.ts +54 -0
- package/templates/api/base/test/repository.test.ts +45 -0
- package/templates/api/base/test/smoke.test.ts +7 -0
- package/templates/api/base/test/stats.test.ts +40 -0
- package/templates/api/base/test/users.test.ts +84 -0
- package/templates/api/base/tsconfig.json +22 -0
- package/templates/api/base/vitest.config.ts +16 -0
- package/templates/api/variants/features/no-github-ci/variant.json +1 -0
- package/templates/api/variants/framework/express/docs/agents/framework-express.md +9 -0
- package/templates/api/variants/framework/express/src/app.ts +37 -0
- package/templates/api/variants/framework/express/src/middleware/errorHandler.ts +13 -0
- package/templates/api/variants/framework/express/src/middleware/validate.ts +59 -0
- package/templates/api/variants/framework/express/src/modules/docs/docs.router.ts +15 -0
- package/templates/api/variants/framework/express/src/modules/health/health.router.ts +7 -0
- package/templates/api/variants/framework/express/src/modules/orders/orders.router.ts +14 -0
- package/templates/api/variants/framework/express/src/modules/products/products.router.ts +24 -0
- package/templates/api/variants/framework/express/src/modules/stats/stats.router.ts +9 -0
- package/templates/api/variants/framework/express/src/modules/users/users.router.ts +24 -0
- package/templates/api/variants/framework/express/src/server.ts +8 -0
- package/templates/api/variants/framework/express/test/errorHandler.test.ts +89 -0
- package/templates/api/variants/framework/express/variant.json +15 -0
- package/templates/fe/base/.agents/skills/ai-rules/SKILL.md +12 -0
- package/templates/fe/base/.agents/skills/plan-feature/SKILL.md +13 -0
- package/templates/fe/base/.agents/skills/review-diff/SKILL.md +12 -0
- package/templates/fe/base/.agents/skills/tdd-step/SKILL.md +14 -0
- package/templates/fe/base/.claude/agents/reviewer.md +21 -0
- package/templates/fe/base/.claude/hooks/format.sh +22 -0
- package/templates/fe/base/.claude/settings.json +41 -0
- package/templates/fe/base/.codex/agents/reviewer.toml +7 -0
- package/templates/fe/base/.codex/config.toml +10 -0
- package/templates/fe/base/.codex/hooks.json +12 -0
- package/templates/fe/base/.env.example +2 -0
- package/templates/fe/base/.github/workflows/ci.yml +37 -0
- package/templates/fe/base/.prettierignore +14 -0
- package/templates/fe/base/.prettierrc +20 -0
- package/templates/fe/base/CLAUDE.md +1 -0
- package/templates/fe/base/README.md +37 -0
- package/templates/fe/base/TASK.md.template +31 -0
- package/templates/fe/base/_gitignore +11 -0
- package/templates/fe/base/docs/agents/base.md +69 -0
- package/templates/fe/base/e2e/users.spec.ts +82 -0
- package/templates/fe/base/eslint.config.js +169 -0
- package/templates/fe/base/index.html +13 -0
- package/templates/fe/base/package.json +63 -0
- package/templates/fe/base/playwright.config.ts +17 -0
- package/templates/fe/base/public/favicon.svg +1 -0
- package/templates/fe/base/public/icons.svg +24 -0
- package/templates/fe/base/scripts/agents-build.ts +26 -0
- package/templates/fe/base/scripts/rules-check.ts +48 -0
- package/templates/fe/base/scripts/rules-sync.ts +35 -0
- package/templates/fe/base/scripts/rules.config.ts +35 -0
- package/templates/fe/base/src/components/shared/EmptyState.tsx +23 -0
- package/templates/fe/base/src/components/shared/ErrorState.tsx +37 -0
- package/templates/fe/base/src/components/shared/PageHeader.tsx +21 -0
- package/templates/fe/base/src/features/users/UserSearch.tsx +48 -0
- package/templates/fe/base/src/features/users/UsersPage.tsx +58 -0
- package/templates/fe/base/src/features/users/UsersTable.tsx +245 -0
- package/templates/fe/base/src/features/users/UsersTableSkeleton.tsx +16 -0
- package/templates/fe/base/src/features/users/__tests__/UserSearch.test.tsx +30 -0
- package/templates/fe/base/src/features/users/__tests__/UsersPage.test.tsx +69 -0
- package/templates/fe/base/src/features/users/__tests__/UsersTable.test.tsx +120 -0
- package/templates/fe/base/src/features/users/__tests__/useUsers.test.tsx +62 -0
- package/templates/fe/base/src/features/users/useUsers.ts +22 -0
- package/templates/fe/base/src/features/users/users.api.ts +22 -0
- package/templates/fe/base/src/features/users/users.schema.ts +43 -0
- package/templates/fe/base/src/lib/apiClient.test.ts +56 -0
- package/templates/fe/base/src/lib/apiClient.ts +42 -0
- package/templates/fe/base/src/lib/appName.ts +2 -0
- package/templates/fe/base/src/lib/env.test.ts +21 -0
- package/templates/fe/base/src/lib/env.ts +26 -0
- package/templates/fe/base/src/lib/queryClient.ts +15 -0
- package/templates/fe/base/src/lib/router.ts +11 -0
- package/templates/fe/base/src/main.tsx +21 -0
- package/templates/fe/base/src/routeTree.gen.ts +77 -0
- package/templates/fe/base/src/routes/__root.tsx +83 -0
- package/templates/fe/base/src/routes/index.tsx +26 -0
- package/templates/fe/base/src/routes/users/index.tsx +24 -0
- package/templates/fe/base/src/stores/seam.test.ts +21 -0
- package/templates/fe/base/src/test/api-mock.ts +22 -0
- package/templates/fe/base/src/test/fixtures/users.ts +39 -0
- package/templates/fe/base/src/test/msw/handlers.ts +9 -0
- package/templates/fe/base/src/test/msw/server.ts +5 -0
- package/templates/fe/base/src/test/render.tsx +45 -0
- package/templates/fe/base/src/test/setup.ts +15 -0
- package/templates/fe/base/tsconfig.json +25 -0
- package/templates/fe/base/vite.config.ts +22 -0
- package/templates/fe/base/vitest.config.ts +21 -0
- package/templates/fe/variants/features/no-github-ci/variant.json +1 -0
- package/templates/fe/variants/features/no-msw/src/test/api-mock.ts +52 -0
- package/templates/fe/variants/features/no-msw/src/test/setup.ts +13 -0
- package/templates/fe/variants/features/no-msw/variant.json +4 -0
- package/templates/fe/variants/features/no-playwright/.github/workflows/ci.yml +19 -0
- package/templates/fe/variants/features/no-playwright/variant.json +5 -0
- package/templates/fe/variants/state/jotai/docs/agents/state-jotai.md +5 -0
- package/templates/fe/variants/state/jotai/src/stores/index.ts +15 -0
- package/templates/fe/variants/state/jotai/src/stores/ui.atoms.test.tsx +29 -0
- package/templates/fe/variants/state/jotai/src/stores/ui.atoms.ts +39 -0
- package/templates/fe/variants/state/jotai/variant.json +12 -0
- package/templates/fe/variants/state/zustand/docs/agents/state-zustand.md +5 -0
- package/templates/fe/variants/state/zustand/src/stores/index.ts +12 -0
- package/templates/fe/variants/state/zustand/src/stores/ui.store.test.ts +34 -0
- package/templates/fe/variants/state/zustand/src/stores/ui.store.ts +62 -0
- package/templates/fe/variants/state/zustand/variant.json +5 -0
- package/templates/fe/variants/ui/base-ui/components.json +25 -0
- package/templates/fe/variants/ui/base-ui/docs/agents/ui-base-ui.md +7 -0
- package/templates/fe/variants/ui/base-ui/src/components/ui/alert.tsx +75 -0
- package/templates/fe/variants/ui/base-ui/src/components/ui/button.tsx +57 -0
- package/templates/fe/variants/ui/base-ui/src/components/ui/input.tsx +19 -0
- package/templates/fe/variants/ui/base-ui/src/components/ui/skeleton.tsx +13 -0
- package/templates/fe/variants/ui/base-ui/src/components/ui/table.tsx +113 -0
- package/templates/fe/variants/ui/base-ui/src/index.css +130 -0
- package/templates/fe/variants/ui/base-ui/src/lib/utils.test.ts +10 -0
- package/templates/fe/variants/ui/base-ui/src/lib/utils.ts +1 -0
- package/templates/fe/variants/ui/base-ui/variant.json +23 -0
- package/templates/fe/variants/ui/radix/components.json +25 -0
- package/templates/fe/variants/ui/radix/docs/agents/ui-radix.md +7 -0
- package/templates/fe/variants/ui/radix/src/components/ui/alert.tsx +75 -0
- package/templates/fe/variants/ui/radix/src/components/ui/button.tsx +66 -0
- package/templates/fe/variants/ui/radix/src/components/ui/input.tsx +18 -0
- package/templates/fe/variants/ui/radix/src/components/ui/skeleton.tsx +13 -0
- package/templates/fe/variants/ui/radix/src/components/ui/table.tsx +113 -0
- package/templates/fe/variants/ui/radix/src/index.css +130 -0
- package/templates/fe/variants/ui/radix/src/lib/utils.test.ts +10 -0
- package/templates/fe/variants/ui/radix/src/lib/utils.ts +1 -0
- package/templates/fe/variants/ui/radix/variant.json +23 -0
- package/templates/fullstack/.github/workflows/ci.yml +18 -0
- package/templates/fullstack/AGENTS.md +7 -0
- package/templates/fullstack/CLAUDE.md +1 -0
- package/templates/fullstack/README.md +16 -0
- package/templates/fullstack/_gitignore +11 -0
- package/templates/fullstack/package.json +16 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import request from 'supertest'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
import { HttpError } from '../src/lib/http-error'
|
|
7
|
+
import { errorHandler } from '../src/middleware/errorHandler'
|
|
8
|
+
import { validate } from '../src/middleware/validate'
|
|
9
|
+
|
|
10
|
+
const app = express()
|
|
11
|
+
app.use(express.json())
|
|
12
|
+
app.get('/teapot', () => {
|
|
13
|
+
throw new HttpError(418, 'I am a teapot', { hot: true })
|
|
14
|
+
})
|
|
15
|
+
app.get('/boom', () => {
|
|
16
|
+
throw new Error('secret internals')
|
|
17
|
+
})
|
|
18
|
+
app.get(
|
|
19
|
+
'/echo/:id',
|
|
20
|
+
validate(
|
|
21
|
+
{
|
|
22
|
+
query: z.object({ n: z.coerce.number().int().min(1) }),
|
|
23
|
+
params: z.object({ id: z.uuid() }),
|
|
24
|
+
},
|
|
25
|
+
({ query, params }, _req, res) => {
|
|
26
|
+
res.json({ n: query.n, id: params.id })
|
|
27
|
+
},
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
app.post(
|
|
31
|
+
'/items',
|
|
32
|
+
validate(
|
|
33
|
+
{ body: z.object({ name: z.string().min(1) }) },
|
|
34
|
+
({ body }, _req, res) => {
|
|
35
|
+
res.status(201).json(body)
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
app.use(errorHandler)
|
|
40
|
+
|
|
41
|
+
describe('errorHandler', () => {
|
|
42
|
+
it('maps HttpError to its status and shape', async () => {
|
|
43
|
+
const res = await request(app).get('/teapot')
|
|
44
|
+
expect(res.status).toBe(418)
|
|
45
|
+
expect(res.body).toEqual({ error: 'I am a teapot', details: { hot: true } })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('hides unknown errors behind 500', async () => {
|
|
49
|
+
const res = await request(app).get('/boom')
|
|
50
|
+
expect(res.status).toBe(500)
|
|
51
|
+
expect(res.body).toEqual({ error: 'Internal Server Error' })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('validate', () => {
|
|
56
|
+
const id = '6f1c8b2e-4a7d-4c1e-9b0a-2d3e4f5a6b7c'
|
|
57
|
+
|
|
58
|
+
it('passes typed query and params to the handler', async () => {
|
|
59
|
+
const res = await request(app).get(`/echo/${id}?n=3`)
|
|
60
|
+
expect(res.status).toBe(200)
|
|
61
|
+
expect(res.body).toEqual({ n: 3, id })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('invalid query → 422 with details', async () => {
|
|
65
|
+
const res = await request(app).get(`/echo/${id}?n=0`)
|
|
66
|
+
expect(res.status).toBe(422)
|
|
67
|
+
expect(res.body.error).toBe('Invalid query')
|
|
68
|
+
expect(res.body.details).toEqual([
|
|
69
|
+
{ path: 'n', message: expect.any(String) },
|
|
70
|
+
])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('invalid params → 422', async () => {
|
|
74
|
+
const res = await request(app).get('/echo/not-a-uuid?n=1')
|
|
75
|
+
expect(res.status).toBe(422)
|
|
76
|
+
expect(res.body.error).toBe('Invalid params')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('invalid body → 422', async () => {
|
|
80
|
+
const res = await request(app).post('/items').send({ name: '' })
|
|
81
|
+
expect(res.status).toBe(422)
|
|
82
|
+
expect(res.body.error).toBe('Invalid body')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('valid body → 201', async () => {
|
|
86
|
+
const res = await request(app).post('/items').send({ name: 'x' })
|
|
87
|
+
expect(res.status).toBe(201)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"cors": "^2.8.6",
|
|
4
|
+
"express": "5",
|
|
5
|
+
"helmet": "^8.3.0",
|
|
6
|
+
"pino": "^10.3.1",
|
|
7
|
+
"pino-http": "^11.0.0"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/cors": "^2.8.19",
|
|
11
|
+
"@types/express": "^5.0.6",
|
|
12
|
+
"pino-pretty": "^13.1.3"
|
|
13
|
+
},
|
|
14
|
+
"agents": ["framework-express"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-rules
|
|
3
|
+
description: Consult the company ai-rules mirrored in docs/ai-rules before writing code that they cover (test selectors, and in the FE template also stack, TanStack, shadcn, a11y). Use whenever a rule might apply.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ai-rules
|
|
7
|
+
|
|
8
|
+
1. `AGENTS.md` → "## ai-rules" table maps topic → `docs/ai-rules/<file>`. Read the relevant file section, not the whole file.
|
|
9
|
+
2. The rules are company-wide and **read-only**: never edit `docs/ai-rules/*`. If a rule looks wrong, propose a change upstream and keep following it here.
|
|
10
|
+
3. A deliberate deviation must be listed in `docs/agents/deviations.md` with the rule § and the reason, then `bun run agents:build`.
|
|
11
|
+
4. `bun run rules:check` fails when the mirror drifted from upstream (`AI_RULES_PATH` or `~/Progetti/ai-rules`): run `bun run rules:sync` and commit `docs/ai-rules/` + `MANIFEST.json` in their own `chore(rules): sync ai-rules` commit.
|
|
12
|
+
5. The "## Non-negotiables" digest in `AGENTS.md` must never contradict `docs/ai-rules/`; the full text wins.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan-feature
|
|
3
|
+
description: Turn a feature request into TASK.md with small verifiable increments (one commit each). Use before writing code for anything bigger than a one-line fix.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# plan-feature
|
|
7
|
+
|
|
8
|
+
1. Copy `TASK.md.template` to `TASK.md` (git-ignored).
|
|
9
|
+
2. Fill **Goal** in one sentence and **Context** with the files that will change (read them first).
|
|
10
|
+
3. Split into increments of ≤ 1 hour each. Each row: what, files, how to verify (a test name or a curl), commit subject in Conventional Commits.
|
|
11
|
+
4. Ordering rule: schema → api → hook (queryOptions) → component → route, test-first when behaviour is clear (see `tdd-step`).
|
|
12
|
+
5. Anything that would deviate from `docs/ai-rules/` goes into the **Deviations** section _before_ implementation.
|
|
13
|
+
6. Show the plan and wait for confirmation only if a decision is not covered by `AGENTS.md`; otherwise start with increment 1.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-diff
|
|
3
|
+
description: Review the working-tree diff against AGENTS.md before committing. Use at the end of every increment or when asked "review".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# review-diff
|
|
7
|
+
|
|
8
|
+
1. `git diff` and `git diff --staged`; read `AGENTS.md`.
|
|
9
|
+
2. Run `bun run verify`; a red verify is finding #1.
|
|
10
|
+
3. Walk the checklist in `.claude/agents/reviewer.md` (contracts, types, runtime, tests, openapi, generated files untouched).
|
|
11
|
+
4. Report only violations, as `path:line — problem — fix`. No praise, no restating the diff.
|
|
12
|
+
5. End with APPROVE / REQUEST CHANGES.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd-step
|
|
3
|
+
description: Execute one increment red → green → refactor → verify → commit. Use for every increment of TASK.md.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# tdd-step
|
|
7
|
+
|
|
8
|
+
1. **Red**: write the failing test first. Hooks/API → `src/features/<f>/__tests__/*.test.tsx` with MSW handlers in `src/test/msw/handlers.ts`; pages → `renderApp('/route')` from `src/test/render.tsx`. Run `bunx vitest run <file>` and confirm it fails for the right reason.
|
|
9
|
+
2. **Green**: minimal code. Data layer = `*.schema.ts` (zod) → `*.api.ts` (apiClient) → `use*.ts` (queryOptions factory); UI composes `src/components/ui/*` wrappers, every interactive element has `data-testid`.
|
|
10
|
+
3. **Refactor** only what the new code touched. State that crosses routes goes in search params, not in a store.
|
|
11
|
+
4. New shadcn component needed → `bunx shadcn@latest add <name>`, never hand-written.
|
|
12
|
+
5. `bun run verify` must be green (typecheck, lint incl. a11y/testid rules, format, AGENTS check, coverage ≥80% on features). `bun run e2e` when a route changed.
|
|
13
|
+
6. One commit: `type(scope): subject` in English, body in Italian explaining _why_. Show `git log --oneline -1`.
|
|
14
|
+
7. Tick the increment in `TASK.md`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Reviews a diff against AGENTS.md conventions and docs/ai-rules. Use after each increment, before committing.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
model: inherit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You review the current diff (`git diff` + `git diff --staged`) of __NAME__. Read `AGENTS.md` first.
|
|
9
|
+
|
|
10
|
+
Check, in this order, and report only violations with file:line and a one-line fix:
|
|
11
|
+
|
|
12
|
+
1. `bun run verify` is green (run it). `bun run e2e` if routes or the users feature changed.
|
|
13
|
+
2. Dogmas: HTTP only via `@/lib/apiClient`; state only via `@/stores`; `@base-ui/*` only in `components/ui`; shadcn components added via CLI, never edited; single `src/index.css`.
|
|
14
|
+
3. `data-testid` on every interactive/stateful element, `feature-element-qualifier`, `{id}` suffix on repeated rows (docs/ai-rules/testing-selectors.md).
|
|
15
|
+
4. a11y: `<caption>` + `aria-sort` on tables, `<nav aria-label>` on pagination, `aria-label` on icon-only buttons, no `<div onClick>`, focus ring never removed (docs/ai-rules/a11y.md, accessibility-legge-stanca.md).
|
|
16
|
+
5. TanStack: `queryOptions` factory + key factory, table state in search params with `validateSearch`, `head.title` on every route.
|
|
17
|
+
6. Types: no `any`, `@ts-ignore`, `enum`, `!`, `React.FC`, `forwardRef`; `import type` where possible.
|
|
18
|
+
7. Tests: feature code has co-located `__tests__` with MSW; coverage stays ≥80% on `src/features`.
|
|
19
|
+
8. Nothing edited by hand in `AGENTS.md`, `docs/ai-rules/`, `src/components/ui/`, `src/routeTree.gen.ts`.
|
|
20
|
+
|
|
21
|
+
Finish with "APPROVE" or "REQUEST CHANGES" and, if changes, the commit message subject you would use.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PostToolUse hook: prettier + eslint --fix on the file just edited.
|
|
3
|
+
# Reads $CLAUDE_FILE_PATH, falls back to the hook JSON on stdin. Exit 0 on anything not lintable.
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
file="${CLAUDE_FILE_PATH:-}"
|
|
7
|
+
if [ -z "$file" ] && [ ! -t 0 ]; then
|
|
8
|
+
file="$(bun -e 'const s = await Bun.stdin.text(); try { const j = JSON.parse(s); console.log(j.tool_input?.file_path ?? j.file_path ?? "") } catch { console.log("") }')"
|
|
9
|
+
fi
|
|
10
|
+
[ -n "$file" ] && [ -f "$file" ] || exit 0
|
|
11
|
+
|
|
12
|
+
case "$file" in
|
|
13
|
+
*.ts|*.tsx|*.js|*.jsx|*.json|*.md|*.yaml|*.yml|*.css) ;;
|
|
14
|
+
*) exit 0 ;;
|
|
15
|
+
esac
|
|
16
|
+
|
|
17
|
+
cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 0
|
|
18
|
+
bunx prettier --write --log-level warn "$file" || true
|
|
19
|
+
case "$file" in
|
|
20
|
+
*.ts|*.tsx|*.js|*.jsx) bunx eslint --fix --no-warn-ignored "$file" || true ;;
|
|
21
|
+
esac
|
|
22
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"permissions": {
|
|
4
|
+
"allow": [
|
|
5
|
+
"Bash(bun run:*)",
|
|
6
|
+
"Bash(bun install:*)",
|
|
7
|
+
"Bash(bun add:*)",
|
|
8
|
+
"Bash(bunx vitest:*)",
|
|
9
|
+
"Bash(bunx eslint:*)",
|
|
10
|
+
"Bash(bunx prettier:*)",
|
|
11
|
+
"Bash(bunx tsc:*)",
|
|
12
|
+
"Bash(bunx playwright:*)",
|
|
13
|
+
"Bash(bunx shadcn:*)",
|
|
14
|
+
"Bash(git status:*)",
|
|
15
|
+
"Bash(git diff:*)",
|
|
16
|
+
"Bash(git log:*)",
|
|
17
|
+
"Bash(git add:*)",
|
|
18
|
+
"Bash(git commit:*)"
|
|
19
|
+
],
|
|
20
|
+
"deny": [
|
|
21
|
+
"Bash(git push:*)",
|
|
22
|
+
"Bash(rm -rf:*)",
|
|
23
|
+
"Read(.env)",
|
|
24
|
+
"Read(.env.*)",
|
|
25
|
+
"Edit(AGENTS.md)",
|
|
26
|
+
"Edit(docs/ai-rules/**)",
|
|
27
|
+
"Edit(src/components/ui/**)",
|
|
28
|
+
"Edit(src/routeTree.gen.ts)"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"hooks": {
|
|
32
|
+
"PostToolUse": [
|
|
33
|
+
{
|
|
34
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{ "type": "command", "command": "bash .claude/hooks/format.sh" }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
name = "reviewer"
|
|
2
|
+
description = "Reviews the diff against AGENTS.md conventions and docs/ai-rules."
|
|
3
|
+
instructions = """
|
|
4
|
+
Read AGENTS.md, then `git diff` and `git diff --staged`. Run `bun run verify`.
|
|
5
|
+
Report only violations as `path:line — problem — fix`, following the checklist in .claude/agents/reviewer.md.
|
|
6
|
+
End with APPROVE or REQUEST CHANGES.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Codex CLI project config. AGENTS.md is picked up natively; hooks in hooks.json mirror .claude/settings.json.
|
|
2
|
+
model = "gpt-5-codex"
|
|
3
|
+
approval_policy = "on-request"
|
|
4
|
+
sandbox_mode = "workspace-write"
|
|
5
|
+
|
|
6
|
+
[projects."."]
|
|
7
|
+
trust_level = "trusted"
|
|
8
|
+
|
|
9
|
+
[agents]
|
|
10
|
+
reviewer = "agents/reviewer.toml"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
verify:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: oven-sh/setup-bun@v2
|
|
14
|
+
with:
|
|
15
|
+
bun-version: latest
|
|
16
|
+
- run: bun install --frozen-lockfile
|
|
17
|
+
- run: bun run rules:check
|
|
18
|
+
- run: bun run verify
|
|
19
|
+
- run: bun run build
|
|
20
|
+
|
|
21
|
+
e2e:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: oven-sh/setup-bun@v2
|
|
26
|
+
with:
|
|
27
|
+
bun-version: latest
|
|
28
|
+
- run: bun install --frozen-lockfile
|
|
29
|
+
- run: bunx playwright install --with-deps chromium
|
|
30
|
+
- run: bun run e2e
|
|
31
|
+
env:
|
|
32
|
+
CI: 'true'
|
|
33
|
+
- uses: actions/upload-artifact@v4
|
|
34
|
+
if: failure()
|
|
35
|
+
with:
|
|
36
|
+
name: playwright-report
|
|
37
|
+
path: playwright-report
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"semi": false,
|
|
3
|
+
"singleQuote": true,
|
|
4
|
+
"printWidth": 80,
|
|
5
|
+
"tabWidth": 2,
|
|
6
|
+
"trailingComma": "all",
|
|
7
|
+
"bracketSpacing": true,
|
|
8
|
+
"arrowParens": "always",
|
|
9
|
+
"endOfLine": "lf",
|
|
10
|
+
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
|
11
|
+
"importOrder": [
|
|
12
|
+
"^react",
|
|
13
|
+
"^@tanstack/(.*)$",
|
|
14
|
+
"<THIRD_PARTY_MODULES>",
|
|
15
|
+
"^@/(.*)$",
|
|
16
|
+
"^[./]"
|
|
17
|
+
],
|
|
18
|
+
"importOrderSeparation": true,
|
|
19
|
+
"importOrderSortSpecifiers": true
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
React 19 + Vite 7 + TanStack Router/Query/Table + Tailwind v4 + shadcn (Base UI) frontend template. Consumes [`simone-api-template`](../simone-api-template) and is the `fe/base` pilot of the `create-simone-app` generator.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
cp .env.example .env
|
|
10
|
+
bun run dev # http://localhost:3000, /api proxied to the API on :4000
|
|
11
|
+
bun run verify # typecheck + lint + format + AGENTS check + tests (coverage ≥ 80% on src/features)
|
|
12
|
+
bun run e2e # Playwright (chromium), API mocked
|
|
13
|
+
E2E_REAL_API=1 bun run e2e # against simone-api-template on :4000
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What is inside
|
|
17
|
+
|
|
18
|
+
- `/users`: server-side table (search, sort, pagination in the URL), skeleton / empty / error states, density toggle (Jotai), theme toggle (Zustand).
|
|
19
|
+
- `src/lib/apiClient.ts`: single axios instance, `{ error, details }` → `ApiError`.
|
|
20
|
+
- `src/features/users/`: zod schema → `getUserList` → `usersQueryOptions` → `UsersTable` / `UserSearch` / `UsersPage`, tests with MSW.
|
|
21
|
+
- ESLint enforces the dogmas: forbidden libraries, `axios`/`zustand`/`jotai`/`@base-ui` only behind their seams, jsx-a11y `error` set, `data-testid` required on interactive elements (AST rule).
|
|
22
|
+
|
|
23
|
+
## AI harness
|
|
24
|
+
|
|
25
|
+
- `AGENTS.md` (generated from `docs/agents/*.md` via `bun run agents:build`) is the single source of conventions; `CLAUDE.md` just includes it. Fragments: `base`, `state-zustand`, `state-jotai`, `ui-base-ui`, `deviations`.
|
|
26
|
+
- Company **ai-rules** mirrored read-only in `docs/ai-rules/` with `MANIFEST.json`; `bun run rules:check` fails on drift, `bun run rules:sync` refreshes from `$AI_RULES_PATH` (default `~/Progetti/ai-rules`).
|
|
27
|
+
- Skills in `.agents/skills/` (Claude Code via the `.claude/skills` symlink, Codex): `ai-rules`, `plan-feature`, `tdd-step`, `review-diff`.
|
|
28
|
+
- `.claude/settings.json`: permissions + PostToolUse hook `format.sh` (prettier + eslint --fix); `.codex/` mirrors hook and reviewer agent.
|
|
29
|
+
- `TASK.md.template`: per-feature plan, one commit per increment.
|
|
30
|
+
|
|
31
|
+
## Deviations from ai-rules
|
|
32
|
+
|
|
33
|
+
See the `## Deviations from ai-rules` section of `AGENTS.md` (Base UI instead of Radix; Jotai alongside Zustand; `cn` package).
|
|
34
|
+
|
|
35
|
+
## Scripts
|
|
36
|
+
|
|
37
|
+
`dev` · `build` · `preview` · `test` · `test:watch` · `e2e` · `lint` · `lint:fix` · `format` · `format:check` · `typecheck` · `verify` · `rules:sync` · `rules:check` · `agents:build` · `agents:check`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Task: <title>
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
<one sentence: what changes for the user>
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
- Files: <src/features/…, src/routes/…, src/test/msw/handlers.ts>
|
|
10
|
+
- Related rules: <docs/ai-rules/… §…>
|
|
11
|
+
|
|
12
|
+
## Increments
|
|
13
|
+
|
|
14
|
+
| # | What | Files | Verify | Commit |
|
|
15
|
+
|---|---|---|---|---|
|
|
16
|
+
| 1 | | | `bunx vitest run src/features/…` | `feat(scope): …` |
|
|
17
|
+
| 2 | | | | |
|
|
18
|
+
|
|
19
|
+
## Definition of done
|
|
20
|
+
|
|
21
|
+
- [ ] `bun run verify` green on every commit (coverage ≥80% on `src/features`)
|
|
22
|
+
- [ ] `bun run e2e` green if a route changed
|
|
23
|
+
- [ ] Every interactive/stateful element has a `data-testid`
|
|
24
|
+
- [ ] Table/pagination/dialog a11y patterns from `docs/ai-rules/a11y.md`
|
|
25
|
+
- [ ] `git log --oneline` shows one commit per increment
|
|
26
|
+
|
|
27
|
+
## Deviations from ai-rules
|
|
28
|
+
|
|
29
|
+
| Rule § | Deviation | Why |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| | | |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
React 19 + Vite 7 + TanStack (Router/Query/Table) + Tailwind v4 + shadcn frontend template.
|
|
4
|
+
Consumes `simone-api-template` (`{ data, page, limit, total }`) and is the `fe/base` pilot of
|
|
5
|
+
the `create-simone-app` generator. Standalone: no OIDC, no mkcert, no MFE, no error routes (not included on purpose).
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | What |
|
|
10
|
+
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `bun run dev` | Vite on :3000 (strict), `/api` proxied to :4000 |
|
|
12
|
+
| `bun run verify` | typecheck + lint + format + AGENTS check + tests (coverage ≥80% on `src/features`) — green before every commit |
|
|
13
|
+
| `bun run e2e` | Playwright chromium; API mocked unless `E2E_REAL_API=1` |
|
|
14
|
+
| `bunx shadcn@latest add <name>` | the only way to add a UI component |
|
|
15
|
+
| `bun run rules:sync` / `rules:check` | mirror ai-rules into `docs/ai-rules/` + `MANIFEST.json` / fail on drift |
|
|
16
|
+
| `bun run agents:build` | regenerate this file from `docs/agents/*.md` (never edit AGENTS.md by hand) |
|
|
17
|
+
|
|
18
|
+
## Structure
|
|
19
|
+
|
|
20
|
+
| Path | Role |
|
|
21
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
22
|
+
| `src/routes/` | file-based routes (`__root.tsx` skip link + nav + `<main id="main">`); `routeTree.gen.ts` is generated **and committed** |
|
|
23
|
+
| `src/features/<f>/` | `*.schema.ts` (zod), `*.api.ts` (apiClient), `use*.ts` (queryOptions), components, `__tests__/` |
|
|
24
|
+
| `src/components/ui/` | shadcn output (never edit); `src/components/shared/` composed pieces (PageHeader, EmptyState, ErrorState) |
|
|
25
|
+
| `src/lib/` | `apiClient.ts` (single axios), `env.ts` (zod on `import.meta.env`), `queryClient.ts`, `router.ts`, `utils.ts` (`cn`) |
|
|
26
|
+
| `src/stores/` | Zustand store + Jotai atoms, exported only through `index.ts` |
|
|
27
|
+
| `src/test/` | `setup.ts`, `render.tsx` (`renderApp`, `renderWithQuery`), `msw/` handlers shared with e2e |
|
|
28
|
+
| `e2e/` | Playwright specs |
|
|
29
|
+
| `docs/ai-rules/` | read-only mirror of company ai-rules (+ `MANIFEST.json`) |
|
|
30
|
+
| `docs/agents/` | fragments concatenated into `AGENTS.md` |
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
1. Copy `TASK.md.template` → `TASK.md`, split into small increments.
|
|
35
|
+
2. Per increment: failing test (MSW) → minimal code → `bun run verify` green → one Conventional Commit (English subject, Italian body explaining _why_).
|
|
36
|
+
3. Never edit by hand: `docs/ai-rules/*`, `AGENTS.md`, `src/components/ui/*`, `src/routeTree.gen.ts`.
|
|
37
|
+
|
|
38
|
+
## Non-negotiables
|
|
39
|
+
|
|
40
|
+
Stack (core-rules §1): Vite 7, React 19, TanStack Router/Query/Table, Zustand, Tailwind v4, shadcn (neutral), RHF + Zod for forms, Axios via `apiClient`, Lucide, `@/` alias, port 3000, Bun, Vitest, ESLint 9 flat, Prettier + sort-imports.
|
|
41
|
+
|
|
42
|
+
MUST
|
|
43
|
+
|
|
44
|
+
- HTTP only through `@/lib/apiClient`; payloads validated with zod schemas in the feature.
|
|
45
|
+
- `data-testid="feature-element-qualifier"` on every interactive/stateful element; repeated rows `feature-row-{id}`.
|
|
46
|
+
- shadcn components only via `bunx shadcn@latest add`; primitives (`@base-ui/*`) only inside `components/ui`.
|
|
47
|
+
- One CSS file: `src/index.css`. Semantic tokens (`bg-background`, `text-muted-foreground`), `cn()` for merging.
|
|
48
|
+
- Cross-route state (page, sort, filters) in search params with `validateSearch`; server state in TanStack Query via `queryOptions` factories; client state only from `@/stores`.
|
|
49
|
+
- Tables: `<caption>`, `th scope="col"` + `aria-sort`, sort control is a `<button>` with `aria-label`, pagination in `<nav aria-label>` with `aria-current`.
|
|
50
|
+
- Every route sets `head.title`; skip link first focusable; icon-only buttons have `aria-label`.
|
|
51
|
+
- TS strict + `noUncheckedIndexedAccess`; `interface` for props (`XxxProps`), `type` for unions, `as const` objects.
|
|
52
|
+
|
|
53
|
+
MUST NOT
|
|
54
|
+
|
|
55
|
+
- react-router, MUI/antd/chakra, styled-components/emotion, swr/redux, direct `axios`, `zustand`/`jotai` outside `src/stores` (all ESLint errors).
|
|
56
|
+
- `any`, `@ts-ignore`, `enum`, non-null `!`, `React.FC`, `forwardRef`, `if (import.meta.env.MODE …)` branching.
|
|
57
|
+
- `<div onClick>`/`<span onClick>`; `outline-none` without a `focus-visible:ring`; index as `key`; `dangerouslySetInnerHTML`.
|
|
58
|
+
- Server data duplicated in a store; extra `.css` files; hand edits to generated files.
|
|
59
|
+
|
|
60
|
+
## ai-rules
|
|
61
|
+
|
|
62
|
+
| Topic | File |
|
|
63
|
+
| -------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
64
|
+
| stack, React 19, TS, structure, Zustand, apiClient, vite.config, Prettier/ESLint | `docs/ai-rules/core-rules.md` |
|
|
65
|
+
| Router (file-based, search params), Query (queryOptions), Table (server-side) | `docs/ai-rules/tanstack-ecosystem.md` |
|
|
66
|
+
| shadcn setup, Tailwind, single `index.css`, a11y checklist | `docs/ai-rules/ui-shadcn.md` |
|
|
67
|
+
| jsx-a11y config, skip link, focus on route change, tables, pagination | `docs/ai-rules/a11y.md` |
|
|
68
|
+
| `data-testid` naming, dynamic ids, Playwright usage | `docs/ai-rules/testing-selectors.md` |
|
|
69
|
+
| WCAG 2.2 AA baseline, Legge Stanca, `lang="it"`, Italian formats | `docs/ai-rules/accessibility-legge-stanca.md` |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type Page, expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
import { listUsers } from '../src/test/fixtures/users'
|
|
4
|
+
|
|
5
|
+
// Default: the API is mocked with the same fixture/semantics as the unit tests.
|
|
6
|
+
// E2E_REAL_API=1 → hit simone-api-template on :4000 through the Vite proxy.
|
|
7
|
+
const REAL_API = process.env.E2E_REAL_API === '1'
|
|
8
|
+
|
|
9
|
+
const mockApi = async (page: Page) => {
|
|
10
|
+
if (REAL_API) return
|
|
11
|
+
await page.route('**/api/users*', (route) =>
|
|
12
|
+
route.fulfill({ json: listUsers(new URL(route.request().url())) }),
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test.describe('/users', () => {
|
|
17
|
+
test.beforeEach(async ({ page }) => {
|
|
18
|
+
await mockApi(page)
|
|
19
|
+
await page.goto('/users')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('renders the first page of users', async ({ page }) => {
|
|
23
|
+
await expect(page).toHaveTitle(/Utenti/)
|
|
24
|
+
await expect(page.getByTestId('users-table')).toBeVisible()
|
|
25
|
+
await expect(page.getByTestId(/^user-row-/)).toHaveCount(20)
|
|
26
|
+
await expect(page.getByTestId('users-pagination-page-1')).toHaveAttribute(
|
|
27
|
+
'aria-current',
|
|
28
|
+
'page',
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('search narrows the list to the typed user', async ({ page }) => {
|
|
33
|
+
const firstRow = page.getByTestId(/^user-row-/).first()
|
|
34
|
+
const id = (await firstRow.getAttribute('data-testid'))?.replace(
|
|
35
|
+
'user-row-',
|
|
36
|
+
'',
|
|
37
|
+
)
|
|
38
|
+
const name = await firstRow.getByRole('cell').first().innerText()
|
|
39
|
+
|
|
40
|
+
await page.getByTestId('users-search-input').fill(name)
|
|
41
|
+
await expect(page).toHaveURL(/[?&]q=/)
|
|
42
|
+
await expect(page.getByTestId(`user-row-${id}`)).toBeVisible()
|
|
43
|
+
expect(await page.getByTestId(/^user-row-/).count()).toBeLessThan(20)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('clicking a header toggles aria-sort and the URL', async ({ page }) => {
|
|
47
|
+
const nameHeader = page.getByTestId('users-table-sort-name')
|
|
48
|
+
await expect(nameHeader.locator('xpath=ancestor::th')).toHaveAttribute(
|
|
49
|
+
'aria-sort',
|
|
50
|
+
'ascending',
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
await nameHeader.click()
|
|
54
|
+
await expect(page).toHaveURL(/order=desc/)
|
|
55
|
+
await expect(nameHeader.locator('xpath=ancestor::th')).toHaveAttribute(
|
|
56
|
+
'aria-sort',
|
|
57
|
+
'descending',
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
await page.getByTestId('users-table-sort-email').click()
|
|
61
|
+
await expect(page).toHaveURL(/sort=email/)
|
|
62
|
+
await expect(
|
|
63
|
+
page.getByTestId('users-table-sort-email').locator('xpath=ancestor::th'),
|
|
64
|
+
).toHaveAttribute('aria-sort', 'ascending')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('pagination moves to page 2', async ({ page }) => {
|
|
68
|
+
await page.getByTestId('users-pagination-next-button').click()
|
|
69
|
+
await expect(page).toHaveURL(/page=2/)
|
|
70
|
+
await expect(page.getByTestId('users-pagination-page-2')).toHaveAttribute(
|
|
71
|
+
'aria-current',
|
|
72
|
+
'page',
|
|
73
|
+
)
|
|
74
|
+
await expect(page.getByTestId(/^user-row-/).first()).toBeVisible()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('skip link is the first focusable element', async ({ page }) => {
|
|
78
|
+
await expect(page.getByTestId('users-table')).toBeVisible()
|
|
79
|
+
await page.keyboard.press('Tab')
|
|
80
|
+
await expect(page.getByTestId('skip-link')).toBeFocused()
|
|
81
|
+
})
|
|
82
|
+
})
|