diff --git a/client/CLAUDE.md b/client/CLAUDE.md index 6937dbc53..db3c515f9 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -13,7 +13,7 @@ npm run lint:warnings # eslint, failing on any error and on any warning coun npm run lint:pages # enforce the Page pattern (CI gate) npm run lint:rtl # physical left/right sides and rtl-lint-disable comments may only shrink per file (CI gate; --list shows them, --update lowers both baselines) npm run lint:size # no source file or stylesheet past 1000 lines, no test past 2000, counted wrapped at 120 columns; longer ones may only shrink (CI gate; --update lowers the baseline) -npm run lint:format # Prettier: every file outside scripts/format-baseline.json must be formatted, the list only shrinks (CI gate; npx prettier --write , then --update) +npm run lint:format # Prettier: every .ts/.tsx/.mjs/.css file under src/, tests/, e2e/ and scripts/ outside scripts/format-baseline.json must be formatted, the list only shrinks (CI gate; npx prettier --write , then --update) npm run lint:layers # imports go downwards: components never import pages/ or mobile/, nothing under the views imports a view; per-file counts may only shrink (CI gate; --list, --update) npm run lint:offline # no view file (components/mobile/pages/hooks) imports src/api/ beyond scripts/offline-baseline.json, which only shrinks (CI gate; --list, --update) npm run lint:skips # no .only anywhere; skipped/todo/fixme tests per file may only shrink against scripts/skip-baseline.json (CI gate; skipIf/runIf and Playwright's test.skip(condition, 'why') are fine) diff --git a/client/scripts/format-baseline.json b/client/scripts/format-baseline.json index c262ff9c0..cc96c881a 100644 --- a/client/scripts/format-baseline.json +++ b/client/scripts/format-baseline.json @@ -1,4 +1,62 @@ [ + "e2e/auth.setup.ts", + "e2e/create-trip.spec.ts", + "e2e/dashboard.spec.ts", + "e2e/dates.ts", + "e2e/help/admin.guide.ts", + "e2e/help/atlas.guide.ts", + "e2e/help/collections.guide.ts", + "e2e/help/dashboard.guide.ts", + "e2e/help/dawarich-track.ts", + "e2e/help/external.ts", + "e2e/help/fixtures.ts", + "e2e/help/guide.ts", + "e2e/help/journey.guide.ts", + "e2e/help/promote.mjs", + "e2e/help/run.mjs", + "e2e/help/settings.guide.ts", + "e2e/help/trip-bookings.guide.ts", + "e2e/help/trip-collab.guide.ts", + "e2e/help/trip-costs.guide.ts", + "e2e/help/trip-day-detail.guide.ts", + "e2e/help/trip-days.guide.ts", + "e2e/help/trip-files.guide.ts", + "e2e/help/trip-lists.guide.ts", + "e2e/help/trip-map.guide.ts", + "e2e/help/trip-place.guide.ts", + "e2e/help/trip-places.guide.ts", + "e2e/help/trip-roadtrip.guide.ts", + "e2e/help/trip-shared.ts", + "e2e/help/trip-transports.guide.ts", + "e2e/help/trip.guide.ts", + "e2e/help/vacay.guide.ts", + "e2e/helpers.ts", + "e2e/ipad-scroll-1432.spec.ts", + "e2e/login.public.spec.ts", + "e2e/mobile-day-swipe-2051.spec.ts", + "e2e/reorder-arrows.spec.ts", + "e2e/screenshots/auth.shot.ts", + "e2e/screenshots/collab.shot.ts", + "e2e/screenshots/detail.shot.ts", + "e2e/screenshots/dialogs.shot.ts", + "e2e/screenshots/pages.shot.ts", + "e2e/screenshots/plan.shot.ts", + "e2e/screenshots/planner.shot.ts", + "e2e/screenshots/promote.mjs", + "e2e/screenshots/seed.setup.ts", + "e2e/screenshots/seed.ts", + "e2e/screenshots/settings.shot.ts", + "e2e/screenshots/shot.ts", + "e2e/screenshots/shots.setup.ts", + "e2e/server-launch.mjs", + "e2e/trip-planner.spec.ts", + "e2e/urlbar-scroll-1809.spec.ts", + "scripts/build-country-shapes.mjs", + "scripts/check-gl-split.mjs", + "scripts/check-page-pattern.mjs", + "scripts/generate-icons.mjs", + "scripts/patch-maplibre.mjs", + "scripts/theme-lint.mjs", "src/App.test.tsx", "src/App.tsx", "src/App.versionHandover.test.tsx", diff --git a/client/scripts/format-lint.mjs b/client/scripts/format-lint.mjs index 2f0a7c1b6..161d92b8f 100644 --- a/client/scripts/format-lint.mjs +++ b/client/scripts/format-lint.mjs @@ -8,7 +8,7 @@ * check came in are listed in scripts/format-baseline.json and the list only * shrinks: every file outside it, a new one in particular, must be formatted, * and a listed file leaves the list once it has been formatted. The check - * covers .ts, .tsx and .css under src/ and tests/. + * covers .ts, .tsx, .mjs and .css under src/, tests/, e2e/ and scripts/. * * npm run lint:format check against the baseline (CI) * npm run lint:format -- --list print every unformatted file diff --git a/client/scripts/lib/format.mjs b/client/scripts/lib/format.mjs index 3e3bd1f96..1814d11cd 100644 --- a/client/scripts/lib/format.mjs +++ b/client/scripts/lib/format.mjs @@ -16,9 +16,10 @@ import { writeBaseline, } from './ratchet.mjs'; -export const DIRS = ['src', 'tests']; +/** The app, its tests, the Playwright specs and the client's own tooling. */ +export const DIRS = ['src', 'tests', 'e2e', 'scripts']; -export const accepts = (key) => /\.(?:tsx?|css)$/.test(key); +export const accepts = (key) => /\.(?:tsx?|mjs|css)$/.test(key); /** * Whether Prettier leaves the file as it is, with the config that applies to diff --git a/client/tests/unit/scripts/format-lint.test.ts b/client/tests/unit/scripts/format-lint.test.ts index 33ffec470..ff6dc57ef 100644 --- a/client/tests/unit/scripts/format-lint.test.ts +++ b/client/tests/unit/scripts/format-lint.test.ts @@ -13,7 +13,14 @@ const FORMATTED = "const a = 'x';\n"; const UNFORMATTED = 'const a = "x"\n'; function run(baseline: string[], files: Record, update = false) { - tree = ratchetTree({ ...CONFIG, [BASELINE]: JSON.stringify(baseline), 'tests/keep.ts': FORMATTED, ...files }); + tree = ratchetTree({ + ...CONFIG, + [BASELINE]: JSON.stringify(baseline), + 'src/keep.ts': FORMATTED, + 'tests/keep.ts': FORMATTED, + 'e2e/keep.spec.ts': FORMATTED, + ...files, + }); return check({ root: tree.root, baselinePath: tree.path(BASELINE), update, ...tree.out }); } @@ -58,13 +65,34 @@ describe('lint:format', () => { expect(JSON.parse(readFileSync(tree.path(BASELINE), 'utf8'))).toEqual([]); }); + it('FORMAT-007: holds the Playwright specs and the client scripts to the same rule', async () => { + const files = { + 'e2e/new.spec.ts': UNFORMATTED, + 'scripts/new-lint.mjs': UNFORMATTED, + 'scripts/data.json': '{"a":1}', + }; + expect(await run([], files)).toBe(1); + const errors = tree.error.join('\n'); + expect(errors).toMatch(/e2e\/new\.spec\.ts is not formatted/); + expect(errors).toMatch(/scripts\/new-lint\.mjs is not formatted/); + expect(errors).not.toMatch(/data\.json/); + tree.remove(); + expect(await run(['e2e/new.spec.ts', 'scripts/new-lint.mjs'], files)).toBe(0); + }); + it('FORMAT-006: a missing baseline or Prettier config stops the check', async () => { - tree = ratchetTree({ ...CONFIG, 'src/a.ts': FORMATTED, 'tests/a.ts': FORMATTED }); + tree = ratchetTree({ + ...CONFIG, + 'src/a.ts': FORMATTED, + 'tests/a.ts': FORMATTED, + 'e2e/a.ts': FORMATTED, + 'scripts/a.mjs': FORMATTED, + }); await expect(check({ root: tree.root, baselinePath: tree.path(BASELINE), ...tree.out })).rejects.toThrow( RatchetError ); tree.remove(); - tree = ratchetTree({ [BASELINE]: '[]', 'src/a.ts': FORMATTED, 'tests/a.ts': FORMATTED }); + tree = ratchetTree({ [BASELINE]: '[]', 'src/a.ts': FORMATTED, 'tests/a.ts': FORMATTED, 'e2e/a.ts': FORMATTED }); // No config in the tree; the lookup climbs no further than the temp directory's parents, // which hold none either. const result = check({ root: tree.root, baselinePath: tree.path(BASELINE), ...tree.out });