Skip to content

Replace Pleasantest with Vitest browser mode - #2421

Merged
spaceninja merged 2 commits into
mainfrom
replace-pleasantest-with-vitest
Aug 21, 2026
Merged

Replace Pleasantest with Vitest browser mode#2421
spaceninja merged 2 commits into
mainfrom
replace-pleasantest-with-vitest

Conversation

@spaceninja

Copy link
Copy Markdown
Member

Overview

Pleasantest has not shipped a release since v5.0.0 in July 2024. Nothing is broken today, but it constrains everything around it: it does not declare support for the Node version we run, and its Puppeteer 22 pin is the reason the CI test job was held back on ubuntu-22.04. It is also a Jest integration, which puts it directly in front of the remaining #2391 work. This moves the suite to Vitest browser mode and drops Jest, Pleasantest and Puppeteer.

Vitest was the natural fit because it transforms through Vite, which we are already on — and that turned out to matter more than the runner itself. The Twig plugin Storybook uses works unchanged in tests, so templates are now imported directly rather than rendered through a filesystem Twing environment the tests built for themselves, and component scripts are imported as modules rather than handed to Pleasantest as source strings. Both of those support files are gone. Tests are split across two projects: the seven that only assert what a template rendered run in Node against jsdom, and the eighteen that need a real accessibility tree, computed layout or genuine focus run in Chromium. Same twenty-five tests, and the suite drops from 9.4s to 8.5s.

Two things are worth a reviewer's attention, both recorded in code comments and the commit message. Card and Overview no longer snapshot the accessibility tree. Both templates deliberately downgrade a header/footer to a div unless the wrapper is a sectioning element, to avoid spurious banner and contentinfo landmarks, and the old snapshots could show that only because the Chromium of the day exposed a scoped header as a banner anyway. Current engines follow the ARIA spec, where a header inside an article is not a landmark at all — so Overview's two variants serialised to an identical three lines and its two tests could no longer fail independently. They now assert which element the template emits, which is the behaviour the templates actually document. Alert's hidden test is a query rather than a snapshot, because Vitest reads an empty inline snapshot as one it has still to write, so the empty snapshot it used to carry would have passed whatever the alert rendered.

One known wart is left in place: four lines of ResizeObserver loop completed with undelivered notifications per run. Elastic Textarea resizes a textarea from inside a ResizeObserver watching that textarea, which is the point of the component, and Chromium reports it in real browsers too. I halved it by dropping Vite's duplicate window-error forwarding, but silencing the rest would mean also swallowing real console errors from tests, which is a worse trade.

Also worth flagging as follow-ups rather than fixing here: Button Swap's test is still named "Swap UI state when clicked" but never clicks, because this package ships that component's markup and not its script — the original test asserted the same snapshot twice as before/after. And Pleasantest's minimum click target-size check has no equivalent in Vitest, so that one guard is gone.

Screenshots

Testing

Chromium is not part of npm install — Playwright downloads browsers separately.

  • Run npx playwright install chromium. It should download a browser, or tell you it is already there.
  • Run npm run build, then npm test. All 25 tests should pass in roughly 8–9 seconds, and the output should label each test with either node or browser (chromium).
  • Confirm the accessibility assertions actually catch a regression: in src/components/alert/alert.twig, change the <p> wrapping the message to a <span> and run npm test again. The Alert tests should fail, and the failure should show that a paragraph was expected where plain text was found. Undo the change.
  • Confirm the same for the non-browser tests: in src/components/card/card.twig, change the first line so the tag defaults to 'div' instead of 'article'. npm test should fail on "should use header/footer with article", reporting a div where an article was expected. Undo the change.
  • Run npm run test:watch, save a change to any test file, and confirm only that file re-runs. Press q to quit.
  • Run npm start and spot-check a few component pages in Storybook — Alert, Card, Sky Nav, Subscribe. They should render as before, and the Twig source preview under each example should still show the template snippet. This shares the Twig plugin with the tests now, and the Babel and TypeScript settings both changed.
  • In the CI run for this PR, confirm the test job runs on ubuntu-latest and that the Playwright install step succeeds.

Pleasantest has not shipped since v5.0.0 in July 2024. It does not declare
support for the Node version we run, and its Puppeteer 22 pin is why the CI
test job was held back on ubuntu-22.04. It also couples us to Jest, which
sits in front of the remaining #2391 work.
Vitest is the natural fit because it transforms through Vite, which we are
already on. That turns out to matter more than the runner choice: the Twig
plugin Storybook uses works unchanged in tests, so `import template from
'./alert.twig'` replaces the filesystem Twing environment the tests built
for themselves in test-utils.ts, and component scripts are imported directly
instead of being passed to Pleasantest as source strings for `utils.runJS`.
Both files are gone, and so are Jest and its Babel transform.
Tests are split across two Vitest projects. Seven of the twenty-five assert
only what a template rendered, so they run in Node against jsdom in
milliseconds; the other eighteen assert accessibility trees, computed layout
or real focus, none of which jsdom provides, and run in Chromium via
Playwright. The split is by filename, `*.browser.test.ts` against
`*.test.ts`. The suite goes from 9.4s to 8.5s, and the remainder is almost
entirely the two deliberate multi-second waits in the Subscribe lifecycle
test, which are unchanged.
The twenty inline accessibility tree snapshots become fourteen ARIA
snapshots plus explicit assertions covering the rest. Three of those
assertions exist because ARIA snapshots record no focus state or accessible
description, which Pleasantest's tree did: the reply textbox's description
and the focus assertions in Comment and Sky Nav were read out of snapshots
before and are asserted directly now.
Card and Overview change more than their serialisation, so they move to the
node project and assert markup. Both templates downgrade a header/footer to
a div unless the wrapper is a sectioning element, precisely to avoid
spurious banner and contentinfo landmarks, and the old snapshots could show
that because the Chromium of the day exposed a scoped header as a banner
anyway. Current engines follow the ARIA spec, where a header inside an
article is not a landmark at all -- so Overview's two variants serialised to
an identical three lines and the tests could no longer fail independently.
Card had a second problem: its demo footer renders `'now'|date(...)`, so a
snapshot that includes text captures the day it was written. What the
templates document is which element they emit, and that is now what is
asserted.
Alert's `hidden` test is a query rather than a snapshot for a related
reason. Vitest reads an empty inline snapshot as one it has still to write,
so the empty snapshot this test used to carry would have passed whatever the
alert rendered.
Notes on the rest:
- moduleResolution moves from "node" to "bundler". The legacy mode cannot
read package exports subpaths, so it could not resolve `vitest/browser`.
- Root-level .ts files have never satisfied the type-aware ESLint rules,
because tsconfig covers `src` and test-utils.ts sat outside it. The Vitest
config and setup files are read through an inferred project instead.
- The browser project sets an 800x600 viewport, which is what Puppeteer
defaulted to and therefore what these tests were written against. Browser
mode's own default is a phone-sized 414x896, which would put every
component on the small-screen side of the 40em breakpoint.
- Vite's forwarding of browser window errors is switched off, because Vitest
reports unhandled errors itself, under a heading that fails the run and
names the test. Console forwarding stays on. Elastic Textarea resizes a
textarea from inside a ResizeObserver watching that same textarea, which
is the point of the component, and Chromium reports the pass it cannot
deliver as a window error -- in real browsers as much as here. Dropping
the duplicate halves the noise; Vite cannot filter it by message.
No changeset: nothing published changes. The bundles are the same size, and
Rollup's entry glob already skips `.test.` files.
@changeset-bot

changeset-botBot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0d66984

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify

netlifyBot commented Aug 21, 2026

Copy link
Copy Markdown

Deploy Preview for cloudfour-patterns ready!

NameLink
🔨 Latest commit0d66984
🔍 Latest deploy loghttps://app.netlify.com/projects/cloudfour-patterns/deploys/6a88ce8cb6c4da0008789d09
😎 Deploy Previewhttps://deploy-preview-2421--cloudfour-patterns.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

The build job failed on this branch: rollup-plugin-dts could not resolve
"./src/components/comment/comment" from the entry `gulp buildTypes`
generates.
TypeScript infers the declaration output layout from the common directory of
everything in the program. Emitting to `ts-dist/src/...`, which that entry
expects, therefore depended on some file outside `src` being part of the
compilation -- and one was, by accident: every test imported
`../../../test-utils.js`, which dragged a project-root file in and pushed the
common directory up to the root. Deleting test-utils.ts left `src` as the
common directory, so declarations moved to `ts-dist/components/...` and the
entry pointed at nothing.
`rootDir` is now set explicitly, so the layout no longer moves when the set
of files changes.
This did not fail locally because `clean` only removed `dist`. A `ts-dist`
from an earlier build still had the old `src` tree in it alongside the new
output, so the entry resolved against stale files. `clean` now removes both,
which is what let the real failure show up in a fresh checkout first.
@spaceninja
spaceninja merged commit cd04c0a into mainAug 21, 2026
8 checks passed
@spaceninja
spaceninja deleted the replace-pleasantest-with-vitest branch August 21, 2026 22:21
spaceninja added a commit that referenced this pull request Aug 24, 2026
Three unrelated leftovers from #2391:
- Button Swap's browser test was named 'Swap UI state when clicked' but
never clicks -- this package ships the markup, not the swap script. Name
it for what it asserts, and assert the half the name claimed but did not
cover: that the swapped state is present and hidden.
- Root-level TypeScript belonged to no tsconfig project, so the type-aware
lint rules could not see it. #2421 worked around that with a
hand-maintained allowDefaultProject list, which is what let test-utils.ts
go unlinted. Add root-level globs to tsconfig instead, so the next file is
covered automatically and gets our real strictness rather than an inferred
project's defaults. This pulled twing/vite-plugin-twig.mjs into the
program under checkJs, hence the annotations there.
- src/index-with-dependencies.scss was the last @import in the repo. Dart
Sass 3 removes @import.
Fixes#2431
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace Pleasantest in the component test suite

1 participant

@spaceninja