From 9e593a3b59cfc7e74867a395d212818263bbf5b2 Mon Sep 17 00:00:00 2001 From: Asif Ansari Date: Mon, 26 Oct 2026 23:09:04 +0530 Subject: [PATCH] ci: check lib/types exports and time-limit the Playwright install - publicApi.test.ts: every type declared in lib/types/* must be exported from lib/index.ts (GridAi*/GridAgent* from /ai), except an explicit INTERNAL_TYPES list, which must itself name existing types. - ci.yml: timeout-minutes 10 on the Playwright browser install, so a stalled download fails fast instead of holding the job for 40 minutes. --- .github/workflows/ci.yml | 2 ++ CLAUDE.md | 1 + lib/publicApi.test.ts | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44f2d994..bc7359d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,8 @@ jobs: - name: Install Playwright browsers if: steps.playwright-cache.outputs.cache-hit != 'true' + # A stalled download otherwise holds the job until its 40-minute timeout (seen on PR #15). + timeout-minutes: 10 run: npx playwright install --with-deps chromium firefox webkit - name: Install Playwright system dependencies diff --git a/CLAUDE.md b/CLAUDE.md index 3c3b2804..0cfc41d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -127,6 +127,7 @@ Full doc: `docs/architecture/grid-row-meta.md` - Anything that depends on layout, `ResizeObserver`, or scrolling (virtualization, container height, drag) must be a `*.browser.test.tsx` — jsdom has no layout engine and reports a 0px viewport (the grid falls back to 600px), so such bugs are invisible there - Browser tests run in Chromium, Firefox and WebKit: `npx playwright install chromium firefox webkit` once. One engine only: `npx vitest run --project browser --browser.name=firefox` - Browser test files run one at a time (`fileParallelism: false`): several test pages open at once in one Firefox steal focus and pointer input from each other +- `lib/publicApi.test.ts` fails when a type declared in `lib/types/*` is not exported from `lib/index.ts` (`GridAi*` / `GridAgent*` from `/ai`). A type that is internal on purpose goes in `INTERNAL_TYPES` with a comment - **Vitest vs Playwright Test:** render `` with props and assert → Vitest (`*.test.tsx`, or `*.browser.test.tsx` for layout). Needs the built package, a real app, a dev/preview server or a file download → the e2e package smoke suite (`e2e/`, `npm run test:smoke`). Vitest aliases the package to `lib/`, so only the smoke suite catches packaging bugs (bundled `react/jsx-runtime`, missing CSS in `dist/`, `.d.ts` that fails `skipLibCheck: false`) - Smoke suite: `scripts/smoke.mjs` builds + packs to `.pack/`, `npm ci`s each fixture in `e2e/fixtures/{react19,react18,nopeers,nextjs}` and unpacks the tarball there, builds them (strict tsc + vite), then runs `e2e/smoke.spec.ts` in Chromium/Firefox/WebKit × both fixtures against `vite preview`. Scenarios via `?scenario=` (`basic`, `flex`, `grouping`, `editing`; React 19 also `pinned`, `dark`). Any console error/warning fails a test. `nopeers` is a webpack 5 app without `exceljs`/`jspdf` (`nopeers.spec.ts`, port 4321): every optional-peer `import()` must stay inside a `try` block, which webpack and Turbopack treat as optional (Vite tolerates a missing peer anyway, so the Vite fixtures cannot catch this). `nextjs` is a Next.js 16 App Router app (Turbopack, `next build` + `next start`, port 4323, `e2e/nextjs.spec.ts`): Server Component → `'use client'` grid, stylesheet in the layout, no optional peers, no aliases; its `next.config.ts` only pins `turbopack.root` to the fixture so the repo's own `exceljs` is not found. A specifier hidden in a variable (`import(name)`) builds but breaks apps that do install the peers: browsers cannot resolve bare specifiers, and Vite's lib build strips `webpackIgnore` / `@vite-ignore` comments. Fixtures are outside root lint/typecheck/vitest; their lockfiles must not carry `integrity` for the tarball entry. Guide: `docs/contributing/testing.md` - Keep browser tests engine-neutral: no CDP (`cdp()` is Chromium-only; use a Playwright-backed command such as `commands.setColorScheme` from `vitest.config.ts`), Tab-order anchors are text inputs (WebKit on macOS skips buttons on Tab), compare scroll positions with what the browser actually took (Firefox snaps them to device pixels), and wait for CSS animations to finish before measuring. Skip an engine only with `it.skipIf(server.browser === '...')` (`server` from `vitest/browser`) and a comment saying why diff --git a/lib/publicApi.test.ts b/lib/publicApi.test.ts index 2e516460..23b494ee 100644 --- a/lib/publicApi.test.ts +++ b/lib/publicApi.test.ts @@ -146,6 +146,25 @@ function readEntryExports(program: ts.Program, file: string = LIB_INDEX): Set { + const found = new Map(); + for (const file of fs.readdirSync(TYPES_DIR)) { + if (!file.endsWith('.ts') || file === 'index.ts' || file.includes('.test.')) continue; + for (const name of readEntryExports(program, path.join(TYPES_DIR, file))) found.set(name, `lib/types/${file}`); + } + return found; +} + const readDoc = (file: string) => fs.readFileSync(file, 'utf8'); const componentDocs = fs.readdirSync(COMPONENT_DOCS_DIR) .filter((f) => f.endsWith('.md')) @@ -224,6 +243,17 @@ describe('public API matches the reference docs', () => { expect(missing).toEqual([]); }); + it('exports every type declared in lib/types from its entry point, unless it is internal', () => { + const declared = declaredTypeNames(program); + expect(declared.size).toBeGreaterThan(50); + const missing = [...declared] + .filter(([name]) => !INTERNAL_TYPES.has(name)) + .filter(([name]) => !(AI_TYPE_NAME.test(name) ? aiExports : rootExports).has(name)) + .map(([name, file]) => `${name} (${file})`); + expect(missing).toEqual([]); + expect([...INTERNAL_TYPES].filter((name) => !declared.has(name))).toEqual([]); + }); + it('exports every name the docs import from the ai entry point, and the root does not', () => { const imports = documentedImports('/ai'); expect(imports.size).toBeGreaterThan(1);