From ead327be73d6f8f3e73622c86d7bb7111d6c3115 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 8 Oct 2026 12:30:19 +0200 Subject: [PATCH] ci(client): lint:format covers the Playwright specs and the client scripts The check only looked at src/ and tests/, so a new spec under e2e/ or a new script under scripts/ could land unformatted, and the ratchet scripts themselves did (fixed in the previous commit). It now walks e2e/ and scripts/ too and takes .mjs alongside .ts, .tsx and .css. The 58 files there that were unformatted before this branch (the e2e specs and help guides, the older build and lint scripts) join the baseline, which keeps only shrinking; JSON baselines stay out of scope. --- client/CLAUDE.md | 2 +- client/scripts/format-baseline.json | 58 +++++++++++++++++++ client/scripts/format-lint.mjs | 2 +- client/scripts/lib/format.mjs | 5 +- client/tests/unit/scripts/format-lint.test.ts | 34 ++++++++++- 5 files changed, 94 insertions(+), 7 deletions(-) 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 });