diff --git a/client/CLAUDE.md b/client/CLAUDE.md index 91ea70f18..c055d0689 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -12,7 +12,7 @@ npm run lint # eslint . (lint:check in CI) npm run lint:warnings # eslint, failing on any error and on any warning count per rule above scripts/eslint-baseline.json (app code, tests and eslint-disable'd messages counted apart; --update lowers it) 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:size # no source file or stylesheet past 1000 lines, no test (Playwright specs and fixtures under e2e/ included) past 2000, counted wrapped at 120 columns; longer ones may only shrink (CI gate; --update lowers the baseline) 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) diff --git a/client/scripts/lib/size.mjs b/client/scripts/lib/size.mjs index cbd878195..af76b097a 100644 --- a/client/scripts/lib/size.mjs +++ b/client/scripts/lib/size.mjs @@ -24,13 +24,15 @@ export const WIDTH = 120; /** * The kinds of file the check covers, each with the size a file without a * baseline entry may reach. Tests get more room: a spec mirrors the component - * it covers and holds fixtures, but past LIMIT it is a god file too. + * it covers and holds fixtures, but past LIMIT it is a god file too. The + * Playwright specs and their fixtures under e2e/ count as tests. */ export const GROUPS = [ { name: 'test', limit: 2000, - accepts: (key) => /\.tsx?$/.test(key) && (key.startsWith('tests/') || TEST_FILE.test(key)), + accepts: (key) => + /\.tsx?$/.test(key) && (key.startsWith('tests/') || key.startsWith('e2e/') || TEST_FILE.test(key)), }, { name: 'source', @@ -40,7 +42,7 @@ export const GROUPS = [ { name: 'stylesheet', limit: 1000, accepts: (key) => key.startsWith('src/') && key.endsWith('.css') }, ]; -export const DIRS = ['src', 'tests']; +export const DIRS = ['src', 'tests', 'e2e']; export function groupOf(key) { return GROUPS.find((group) => group.accepts(key)) ?? null; diff --git a/client/scripts/size-baseline.json b/client/scripts/size-baseline.json index 39a9323ab..88d00cfe6 100644 --- a/client/scripts/size-baseline.json +++ b/client/scripts/size-baseline.json @@ -1,4 +1,5 @@ { + "e2e/help/fixtures.ts": 2291, "src/api/client.ts": 1916, "src/components/Admin/AdminPluginsPanel.test.tsx": 2704, "src/components/Admin/AdminPluginsPanel.tsx": 2431, diff --git a/client/scripts/size-lint.mjs b/client/scripts/size-lint.mjs index 6656299e9..48ddf624e 100644 --- a/client/scripts/size-lint.mjs +++ b/client/scripts/size-lint.mjs @@ -4,8 +4,8 @@ * * A file past a thousand lines holds several concerns that no longer fit in * one reading, and every change to one of them risks the others. Each source - * file and stylesheet under src/ may hold 1000 lines, each test (under tests/ - * or a co-located *.test.*) 2000. Lines are counted as an editor wraps them + * file and stylesheet under src/ may hold 1000 lines, each test (under tests/, + * a Playwright spec or fixture under e2e/, or a co-located *.test.*) 2000. Lines are counted as an editor wraps them * at 120 columns, so joining lines does not make a file smaller. The files * that were already larger are listed in scripts/size-baseline.json with the * size they had, and the check fails when one of them grows past its entry. A diff --git a/client/tests/unit/scripts/size-lint.test.ts b/client/tests/unit/scripts/size-lint.test.ts index cab635499..b0065374e 100644 --- a/client/tests/unit/scripts/size-lint.test.ts +++ b/client/tests/unit/scripts/size-lint.test.ts @@ -10,7 +10,12 @@ afterEach(() => tree?.remove()); const BASELINE = 'scripts/size-baseline.json'; function run(baseline: Record, files: Record, update = false) { - tree = ratchetTree({ [BASELINE]: JSON.stringify(baseline), 'tests/keep.test.ts': lines(1), ...files }); + tree = ratchetTree({ + [BASELINE]: JSON.stringify(baseline), + 'tests/keep.test.ts': lines(1), + 'e2e/keep.spec.ts': lines(1), + ...files, + }); return check({ root: tree.root, baselinePath: tree.path(BASELINE), update, ...tree.out }); } @@ -31,6 +36,8 @@ describe('lint:size', () => { expect(groupOf('src/index.css')).toMatchObject({ name: 'stylesheet', limit: 1000 }); expect(groupOf('src/a/B.test.tsx')).toMatchObject({ name: 'test', limit: 2000 }); expect(groupOf('tests/unit/x.ts')).toMatchObject({ name: 'test', limit: 2000 }); + expect(groupOf('e2e/help/fixtures.ts')).toMatchObject({ name: 'test', limit: 2000 }); + expect(groupOf('e2e/server-launch.mjs')).toBeNull(); expect(groupOf('src/a/notes.md')).toBeNull(); }); @@ -56,6 +63,13 @@ describe('lint:size', () => { expect(tree.error.join('\n')).toMatch(/src\/A\.test\.tsx: 2001 lines .*test file is 2000/); }); + it('SIZE-010: covers the Playwright specs and fixtures under e2e/', () => { + const files = { 'src/a.ts': lines(1), 'e2e/help/fixtures.ts': lines(2001), 'e2e/a.spec.ts': lines(2000) }; + expect(run({}, files)).toBe(1); + expect(tree.error.join('\n')).toMatch(/e2e\/help\/fixtures\.ts: 2001 lines .*test file is 2000/); + expect(tree.error.join('\n')).not.toMatch(/e2e\/a\.spec\.ts/); + }); + it('SIZE-007: holds a baselined file at its entry and fails it once it grows', () => { expect(run({ 'src/big.ts': 1200 }, { 'src/big.ts': lines(1200) })).toBe(0); tree.remove(); @@ -81,7 +95,7 @@ describe('lint:size', () => { }); it('SIZE-009: a broken baseline stops the check', () => { - tree = ratchetTree({ [BASELINE]: '{', 'src/a.ts': lines(1), 'tests/a.ts': lines(1) }); + tree = ratchetTree({ [BASELINE]: '{', 'src/a.ts': lines(1), 'tests/a.ts': lines(1), 'e2e/a.ts': lines(1) }); expect(() => check({ root: tree.root, baselinePath: tree.path(BASELINE), ...tree.out })).toThrow(RatchetError); }); });