Three layers, from fastest to closest to a real consumer.
| Command | What runs | Where the tests live |
|---|---|---|
npm test |
Vitest unit tests in jsdom | lib/**/*.test.ts(x) |
npm run test:browser |
Vitest browser mode in real Chromium, Firefox and WebKit | lib/**/*.browser.test.ts(x) |
npm run test:smoke |
Package smoke suite: Playwright Test against apps that installed the packed tarball | e2e/ |
npm run test:demo |
Demo-site smoke test: builds the demo and loads every route in Playwright | e2e/demo.spec.ts |
Other gates: npm run lint, npm run typecheck (lib, tests, demo, e2e, bench), npm run build:lib (also checks that React stays external).
React Compiler compatibility: npm run check:compiler, and REACT_COMPILER=1 in front of any Vitest command runs it against lib/ compiled. See React Compiler compatibility.
One-time setup for the browser and smoke suites:
npx playwright install chromium firefox webkit- Render
<DataGrid>with props and assert on it → Vitest. Pure logic and hooks in*.test.ts(x); anything that needs layout,ResizeObserver, scrolling or focus in*.browser.test.tsx. - Needs the built package, a real app, a dev/preview server or a file download → e2e smoke. Examples: the published
.d.tscompiles in a strict consumer, the bundle works under React 18, the stylesheet sizes the grid inside an app shell, optional peers (exceljs,jspdf) load from the consumer'snode_modules.
Vitest tests import lib/ source directly, so they cannot catch packaging mistakes (bundled react/jsx-runtime, a missing CSS rule in dist/, broken exports). That is what the smoke suite is for; keep it small.
npm run test:smoke (scripts/smoke.mjs):
npm run build:lib, thennpm packinto.pack/opencorestack-opengridx.tgz.- For each fixture in
e2e/fixtures/(react19,react18,nopeers):npm cifrom its committed lockfile (skipped while the lockfile is unchanged), then unpack the fresh tarball into itsnode_modules. - Build each fixture:
tscin strict mode withskipLibCheck: falseagainst the shipped types, thenvite build. The build is itself a typing test.nopeersis a plain-JavaScript webpack 5 app that does not install the optional export peers (exceljs,jspdf,jspdf-autotable); itswebpackbuild is the check that the peer imports stay optional (Vite builds without them either way, webpack and Turbopack do not unless each peerimport()is inside atryblock). playwright test(e2e/playwright.config.ts): Chromium, Firefox and WebKit againstvite previewof each Vite fixture (ports 4319 and 4318) runningsmoke.spec.ts, andnopeersserved by itsserve.mjs(port 4321; 4320 is the benchmark) runningnopeers.spec.ts(the grid renders, both exports reject with the install hint).
Scenarios are selected with ?scenario=: basic, flex, grouping, editing, range (both fixtures), pinned, dark (React 19 only). Every test fails on any console error or warning.
Useful variants:
npm run test:smoke -- --skip-lib # reuse dist/ from a previous build:lib
npm run test:smoke -- --skip-tests # pack, install and build the fixtures only
npm run test:smoke -- -- --project=chromium-react18 # pass arguments to playwright test
npx playwright test --config e2e/playwright.config.ts # rerun tests against the current fixture builds
npx playwright show-report playwright-report # HTML report, traces of failed tests--fresh ignores the fixture lockfiles and installs the newest versions in range (used by the weekly workflow).
The fixtures are separate npm projects: they are not part of the root lint, typecheck or Vitest runs and install into their own node_modules. When you change a fixture's dependencies, run npm install in it and then delete the integrity field from the node_modules/@opencorestack/opengridx entry of its package-lock.json (the tarball changes on every build; the script refuses a lockfile that pins it).
npm run test:demo runs npm run build (demo site into demo/dist, base /OpenGridX/ as on GitHub Pages) and then playwright test --config e2e/playwright.demo.config.ts, which serves the build with vite preview on port 4322. e2e/demo.spec.ts:
- reads the routes from
examplesConfigindemo/App.tsx(no hand-kept list) and checks that the built sidebar links to exactly those routes; - for each route: loads it, waits until no request has been in flight for 500 ms, and asserts no console error or warning and no uncaught page error, that the lazy chunk replaced the loading skeleton, that
mainhas text, and that every grid root (.ogx) shows a row or an intentional empty, loading or error state.
Every route runs in Chromium; the pages tagged @heavy in the spec (cell selection, AI assistant, full feature test, RTL, theming, pivot, infinite scroll) also run in Firefox and WebKit. A page that logs a warning on purpose goes into ALLOWED_CONSOLE in the spec with a comment saying why. A new demo route needs nothing: add it to examplesConfig and it is covered.
npm run test:demo
npx playwright test --config e2e/playwright.demo.config.ts --project chromium # reuse an existing demo/distnpm run bench (bench/run.mjs) measures the grid at 10k, 100k, 500k and 1M rows. It is a measuring tool, not a test: nothing fails on a number. The method and the published results are in Performance → Benchmarks.
- Packs the library the same way as the smoke suite (
scripts/lib/package-fixture.mjs) and installs it intobench/app, a Vite + React 19 app with its own lockfile (same rule: nointegrityon the tarball entry). - Builds the app for production (strict
tsc, thenvite build) and serves it withvite previewon port 4320. - Drives it with Playwright (headless, 1280×800), one new browser context per run, and writes
bench/results/<date>-<machine>.json.
npm run bench # all engines, 10k/100k/500k/1M, 5 runs + 1 warm-up (about 30 min)
npm run bench -- --quick # 1 run, no warm-up, no 500k
npm run bench -- --rows 100000,1000000 --engine chromium
npm run bench -- --repeat 5,1000000:3 # fewer runs for one size
npm run bench -- --skip-build # reuse the built bench/app
node bench/report.mjs # Markdown tables of the newest full results file
node bench/report.mjs --write docs/performance.md # replace the tables in docs/performance.mdClose other heavy programs first and keep the machine on power; the runner records the load average at the start and end and warns when it is high. Commit a results file only from a full run, and regenerate the tables in docs/performance.md from it. bench/ is outside ESLint and the Vitest projects; npm run typecheck checks its TypeScript (bench/tsconfig.json, resolving the package to lib/).
.github/workflows/ci.yml(pull requests and pushes to branches other thanmain): lint, typecheck, unit, browser tests,build:lib, smoke suite, demo-site smoke test. Playwright reports and traces are uploaded when a step fails.deploy.ymlbuilds the demo throughnpm run test:demo, so a demo page that fails the sweep is never deployed.npm-publish.ymlruns the smoke suite afterbuild:liband beforenpm publish.nightly.ymlruns weekly: all Vitest projects and the smoke suite with--fresh, and a separatebenchjob (npm run bench -- --quick --engine chromium) that puts the tables in the job summary and uploads the JSON as thebench-resultsartifact. It fails only when the benchmark script breaks, never on timings.