You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The @WordPress/gutenberg-components team is leading this migration. The main motivation is Vitest Browser Mode, which lets focused unit and integration tests run in a real browser. Component tests can check CSS, layout, element sizing, focus, events, observers, and other browser APIs without broad jsdom mocks. jsdom stays available for deterministic DOM tests. We are particularly excited about what this enables for UI component testing.
Vitest has been rapidly becoming one of the leading modern JavaScript test runners. Its ESM-first, Vite-native, and Jest-compatible design fits the modern JavaScript ecosystem, aligns with the current Storybook build pipeline, and makes an incremental migration practical. Earlier public discussions had already identified browser-based component testing as a useful direction, while #80738 showed the maintenance cost of Jest's ESM limitations.
The main gains are real-browser coverage, modern ESM support, explicit test environments, and a more maintainable toolchain. The historical measurements below describe the performance tradeoff; they do not establish an overall speed improvement over Jest.
What changes in practice
For most people working in Gutenberg, very little changes day to day. The existing npm test and npm run test:unit commands remain, and most tests keep the same overall structure because Vitest provides Jest-compatible APIs. The visible changes are:
Filenames select the test environment. Tests without an environment suffix run in Node. Use *.jsdom.test.* for jsdom and *.browser.test.* for Browser Mode. Repository checks enforce the convention for new and changed tests.
Tests use Vitest APIs with explicit imports. Some mocks, queries, assertions, and command-line options change where the runners differ.
Tests that depend on real CSS, layout, element geometry, or browser behavior move to Browser Mode.
Jest-specific configuration and packages are removed once the migration is complete.
Tests run through Vite, as Storybook does. This migration does not replace the development or production build system.
Most changes affect test tooling. The @wordpress/dom visibility fix landed in #82574, the generated-style injection fix in @wordpress/build landed in #82154, and #82687 also fixed useResizeObserver cleanup on unmount. Preserve their regression coverage while finishing the migration.
Consumers of WordPress test tooling do have a package-level migration. Existing npm releases remain available, but Gutenberg will stop publishing updates to @wordpress/jest-preset-default and @wordpress/jest-console. Their replacements are @wordpress/vitest-preset-default and @wordpress/vitest-console, and @wordpress/scripts test-unit-js will run Vitest. Publishing the new packages is additive. Switching existing public commands and defaults requires a documented tooling major release.
Goal
Move Gutenberg-owned JavaScript unit and integration tests, plus published WordPress test tooling, from Jest to Vitest. Preserve test behavior and CI coverage, then remove active Jest runner infrastructure. Trunk already uses Vitest 5 and Node 24. Finish the remaining tooling cutover and cleanup against that baseline.
Vitest uses Node by default. Tests that need deterministic DOM semantics use *.jsdom.test.*. Tests that need real CSS, layout, browser APIs, or browser interaction use *.browser.test.* and Vitest Browser Mode in Chromium.
Current status
Reassessed on September 11, 2026 through #82761. The repository-owned test migration and the first cleanup are complete. The tracking issue remains open for the public tooling cutover and removal of active Jest infrastructure.
The foundation, repository migration, and first cleanup are merged:
Migrates the final six Jest-listed suites, moves more rendered behavior to Browser Mode, and strengthens the Vitest policy checks. The Jest allowlist is now empty.
Removes obsolete repository-only Jest setup, transforms, browser emulation, Testing Library setup, and Emotion/snapshot serializers. It preserves native Vite block.json invalidation coverage and supported public Jest tooling.
Current trunk routes all 1,145 discovered test files to Vitest and zero to Jest. The #82761 verification passed all 97 Browser files and 1,315 tests. CI still runs four Node/jsdom shards for each of Node 24 and Node 26, plus one Browser job on Node 24.
The overall migration is not complete. npm run test:unit still launches Jest through wp-scripts test-unit-js, although the empty allowlist now makes it exit successfully with no tests. @wordpress/jest-preset-default, @wordpress/jest-console, the remaining repository Jest configuration, and compatibility probes also remain. The planned @wordpress/vitest-preset-default and @wordpress/vitest-console packages have not been added or published. The remaining work is organized into three PRs below.
Next PRs, in execution order
These start after #82761. Each numbered item is one PR. Open these as drafts and base each dependent PR on its predecessor until that predecessor merges. Reuse existing implementation and fixtures where they still fit current trunk. Publication is a release gate within this sequence, not an additional PR or a reason to delay preparing later changes.
Add the public Vitest packages and prepare their additive release.
Add @wordpress/vitest-console and @wordpress/vitest-preset-default for Vitest 5, with package exports, accurate synchronous/asynchronous matcher types, public engine/peer ranges, package registration, changelogs and migration documentation. Use Node by default, suffix-selected jsdom and Chromium projects, CSS-module proxies in Node/jsdom, and real styles in Browser Mode. Preserve explicit imports, isolation and suite-owned browser mocks. Any public Vitest lint configuration introduced here must be opt-in; existing Jest lint defaults and wp-scripts test-unit-js behavior stay available.
Verify packed packages in isolated consumers across the supported public Node/Vite combinations, including JSX/Emotion compilation, console matchers, config/setup behavior, failure output and native browser values. Cover both rebuilt CSS-module and regular-CSS output from @wordpress/build, using its merged browser-safe guard. Confirm existing Jest tooling still works. Refresh dependency/security audits and release metadata.
Release gate after this PR: publish and verify the two new packages before adopting them in released public tooling. Confirm the required @wordpress/build fix is also available in a published version and rebuild affected generated CSS. The source fix already landed in Build: Fix regular CSS injection in Vitest Browser Mode #82154; no additional build-fix PR is planned. Publishing these packages does not require switching wp-scripts or removing Jest.
Adopt the published packages and switch the supported test tooling.
Use the published replacements in Gutenberg's shared test setup and @wordpress/scripts integration. Verify actual npm artifacts in isolated consumers and record their versions and public support ranges. Make wp-scripts test-unit-js discover consumer Vitest/Vite configuration and otherwise use the WordPress preset. Switch the relevant public lint defaults, update migration guidance and release metadata, and verify the default, watch, debug and update commands. Converge repository commands so npm test runs the Vitest suite once rather than invoking it through two aliases.
Test supported public Node/Vite combinations, config discovery, explicit jsdom/Browser opt-in and generated CSS behavior. Preserve the current Node 24/26 CI matrix, the single Chromium job, timezone coverage and Storybook smoke coverage. Make intentional public command/default changes at a documented major release boundary. Define the removal boundary for any deliberately retained Jest compatibility command; do not ask consumers to adopt a temporary command that immediately disappears.
Release gate after this PR: publish the consumer switch and verify the supported consumers against that release before final Jest retirement. Later implementation can be prepared in advance, but the replacement must be available before its compatibility paths are removed.
Remove the remaining active Jest infrastructure.
After the replacement tooling has shipped and supported in-repository consumers have migrated, remove obsolete Jest package sources, direct dependencies, commands, configuration, transforms, reporters, types, lint rules, compatibility probes and migration metadata. Honor any public removal boundary documented in PR 2. Regenerate the lockfile without reintroducing the removed Storybook runner or @ariakit/test dependencies.
Keep a permanent no-Jest CI audit and direct filename-based project-discovery tests. Prove that each test is discovered once, default commands execute Vitest once, and no supported developer or CI command launches Jest, including subprocess probes. Preserve fixed-seed file shuffling, @testing-library/jest-dom, the runner-neutral toMatchDiffSnapshot helper, React/SWC/Emotion compilation and local helpers that still have consumers. Remove obsolete Emotion style-snapshot types and dependencies. Run the complete current CI matrix, timezone tests, Storybook build/discovery/smoke coverage, build, typecheck and relevant lint.
The package-release and consumer-adoption gates replace a single coordinated cutover. There is no requirement to wait for every external project to upgrade: document the supported migration boundary, verify representative consumers, and leave existing npm releases available. Optional follow-ups below are outside these three PRs and must not expand their scope.
Keep each of the remaining three PRs within its stated scope and release dependencies. Reuse existing work and regenerate lockfiles against the current baseline without restoring merged-away code.
Before the additive package release, verify supported Node/Vite combinations, package contents, public Vitest 5 matcher types and native Node/jsdom/Browser behavior. Confirm existing Jest tooling still works.
Verify rebuilt CSS-module and regular-CSS fixtures against the published @wordpress/build behavior. Preserve the merged browser-safe guard instead of reviving WP_TESTS_SKIP_STYLE_INJECTION.
After publication, test isolated consumers against the actual npm versions before switching wp-scripts and other consumers. Record the published versions and support boundaries in the adoption PR.
Preserve the current CI matrix and check names: four Node/jsdom shards on each of Node 24 and 26, one Chromium job on Node 24, plus timezone and Storybook coverage. Run relevant routing, conventions, lint, build and typecheck checks for each stage.
Before final retirement, verify all supported in-repository consumers have switched, each test is discovered once, default commands invoke Vitest once and no active Jest runner remains. Preserve fixed-seed file shuffling, runner-neutral toMatchDiffSnapshot and @testing-library/jest-dom.
Refresh dependency/security audits and release metadata for each publication. Make intentional tooling breaks at the documented major boundary.
The manual package workflow and RC1 package publication use the protected WordPress packages environment; latest publication shares the npm-publish-wp-latest concurrency group. Use those existing controls to publish the intended stage. An additive Vitest-package release is allowed before the consumer switch and Jest retirement. Verify the published dependency chain @wordpress/vitest-console -> @wordpress/vitest-preset-default before releasing the @wordpress/scripts version that adopts it.
The historical implementation and performance results below remain useful background. They are not completed checks for the three remaining PRs. The measured Jest-to-Vitest performance tradeoff was accepted previously; no overall speed improvement is claimed.
Architecture and test conventions
Runner ownership and environment selection remain separate while the migration manifest exists. The filename selects the Vitest project.
Routing validation compares live test discovery with the active Jest and Vitest union. Every discovered test has exactly one active runner. Fixed counts, hashes, and added or retired bookkeeping are not part of the contract.
Environment routing is *.browser.test.* to Browser Mode, *.jsdom.test.* to jsdom, and all remaining tests to Node.
Environment selection does not use central Node or jsdom file allowlists.
Vite and SWC compile React and Emotion. Style correctness is not simulated through an Emotion snapshot serializer.
Browser Mode loads real CSS and verifies computed styles, cascade, responsive behavior, layout, geometry, rendered visibility, scrolling, observers, media queries, and browser interaction.
Node and jsdom use the CSS-module proxy. jsdom remains responsible for DOM semantics, events, state, and pure logic.
The merged wp-build guard is typeof process === 'undefined' || process.env.NODE_ENV !== 'test'. Node/jsdom skip automatic injection in test mode; Browser Mode injects because it does not define process. Regular CSS also checks that document exists. There is no WP_TESTS_SKIP_STYLE_INJECTION override.
Test both CSS modules and ordinary CSS, generated class mappings, token fallbacks and deduplication. Older generated regular-CSS output still needs rebuilding to use the browser-safe guard; a new preset cannot rewrite old emitted code.
Use packed @wordpress/build fixtures to verify both generated output paths in isolated jsdom and Browser Mode consumers.
Browser React tests use vitest-browser-react; jsdom React tests use Testing Library React. Shared DOM matchers and permitted query utilities remain. Preserve the merged renderer convention checks.
Deterministic browser signals in jsdom use explicit local mocks that the owning test restores. Shared setup does not install browser behavior.
Browser tests use Vitest Browser Mode userEvent. Four documented fireEvent exceptions remain where synthetic event construction is the behavior under test.
Storybook smoke coverage remains separate from unit-test routing. The static Storybook build and Vitest must discover the same testable story IDs.
Dependencies belong to the workspace that imports them.
Vitest globals remain disabled. Tests use explicit imports.
Merged guardrails and rebase notes
#81040 merged on August 27, 2026. It adds live exact-one-runner routing, explicit environment policy, direct workspace dependency checks, ESM and explicit Vitest import rules, TypeScript test graph validation, stale-exception checks, and empty Jest shard support.
The policy distinguishes deterministic jsdom behavior from browser-dependent layout and style behavior. Its binding analysis covers Vitest and Testing Library aliases, namespaces, screen, render, within, collection queries, callback aliases, object members, bound callbacks, and type-only imports. The merged CI run passed Static Analysis, build, routing, and all four JavaScript unit-test shards.
#81918 removed the Jest-based Storybook runner in parallel. Preserve that cleanup when regenerating the remaining stack lockfiles; do not restore its obsolete transitive dependencies. Rerun the no-Jest invariant and Storybook smoke checks at the final head.
The September 10 source reassessment found @swc/plugin-emotion 16.0.0 and @swc/core 1.16.2 on trunk after #82660. Preserve those updates when refreshing the older public preset and rerun packed consumers at the final head.
#80991 is a mechanical classification step. It renames DOM-dependent tests to *.jsdom.test.*, preserves *.browser.test.*, and leaves Node-compatible tests without an environment suffix. It does not migrate Jest APIs.
Historical final-head verification snapshot
This snapshot predates the merged Storybook cleanup and the current three-PR remainder above. Treat it as historical evidence, not current exact-head verification.
At final stack head ad994712aa11cf1bf612b33facbd0bb535be5995:
the no-Jest audit validated 174 package manifests and 8,554 active source and configuration files;
project discovery validated 1,032 Vitest files, with 969 Node or jsdom files and 63 Browser files;
the inventory contained 35,064 tests, with 33,999 Node or jsdom tests and 1,065 Browser tests, with four skipped;
Browser Mode passed all 63 files and 1,065 tests in Chromium;
published-package consumer fixtures passed with native Node, jsdom, and Browser Mode behavior;
package patches applied after npm ci --ignore-scripts;
The final cleanup removes active Jest packages, runner configuration, transforms, types, rules, the migration manifest, stale @ariakit/test entries, and duplicate root test invocation. Historical changelogs, intentional legacy-result fixtures, runner-neutral @testing-library/jest-dom, and unrelated transitive Jest dependencies remain out of scope.
Dependency, compatibility, and release boundary
At the September 10 trunk snapshot, the lockfile resolves Vite 8.2.2, Vitest and @vitest/browser-playwright 5.0.0, @swc/core 1.16.2, @swc/plugin-emotion 16.0.0, Playwright 1.63.0, vitest-browser-react 2.3.0, Testing Library React 16.3.3, jest-dom 7.0.1 and Flakiness.io Vitest 1.9.2. Refresh the remaining branches without restoring their older resolutions.
The proposed public preset supports Vite 7 or 8. Validate that range with Vitest 5 and the supported public Node versions before publishing; adjust the declared range if a combination is not supported.
jsdom stays at 26.1.0. A jsdom major upgrade is separate work and is not required for this runner migration.
The historical local lockfile audit reported 76 repository advisories: 6 low, 41 moderate, 28 high, and 1 critical. The migration did not add a specific advisory. Refresh this count at the final rebased head.
The internal test migration, JSX renames and environment classification preserve production behavior. Keep focused compatibility checks for the @wordpress/dom visibility fix and the @wordpress/build generated-style contract.
Changing @wordpress/vitest-preset-default to use Node by default is intentional. Consumers must explicitly opt into jsdom or Browser Mode. Publishing the new packages is additive. Changes to the @wordpress/scripts runner, public Node support and ESLint defaults need their own documented major release boundary. Retire Jest support only after the replacement ships and its supported consumers are verified. The repository npm minimum already increased to 11.16.0. Published tooling has its own engine contract; verify it explicitly. Production packages are not being converted wholesale to ESM.
Migration size
The historical sum of the 12 migration PRs against their intended parents was 21,662 added lines and 20,474 removed lines. The aggregate text churn was 42,136 lines, with a net increase of 1,188 lines.
package-lock.json accounted for 1,668 additions and 2,099 deletions. Excluding repeated lockfile churn, the migration added 19,994 lines and removed 18,375 lines, with a net increase of 1,619 lines.
This excluded the parallel #81918 cleanup and did not compare the final head with moving trunk. Refresh these figures only if they remain useful after the final rebase.
Performance measurements and CI layout
Current measurements do not show an overall speed improvement over Jest. The environment split improves Vitest's critical path compared with mixed sharding.
The pinned Vite 8 Jest baseline and the earlier mixed-shard Vitest layout used Node 20.20.2 on the same machine. Dependencies, repository patches, and generated parser files were prepared outside the timed section. Each result is the median of three complete rounds with four fresh-process shards and 11 workers per shard.
Jest: 34.71 seconds on the critical path and 116.51 seconds of summed runner time.
Mixed Vitest: 41.09 seconds on the critical path and 157.60 seconds summed.
Mixed Vitest was 18.4% slower on the critical path and used 35.3% more summed runner time.
Before the thread-pool and setup-scope optimization, Vitest measured 52.36 seconds on the critical path and 195.18 seconds summed. The optimization reduced those results by 21.5% and 19.3%. A matched Node or jsdom comparison measured a further 17.0% critical-path improvement from threads instead of forks.
The historical layout below used four Node/jsdom shards plus one Browser job. Current trunk runs those four shards on both Node 24 and Node 26, plus one Node 24 Browser job. Preserve this matrix, current required check names and the final invariant job. Chromium installation and Browser Mode initialization occur once.
A same-head comparison at ad994712aa11cf1bf612b33facbd0bb535be5995 measured:
mixed shards at 43.73, 46.46, 48.74, and 46.96 seconds, with 48.74 seconds critical and 185.89 seconds summed;
Node or jsdom shards at 38.50, 44.41, 44.22, and 42.26 seconds;
the Browser job at 24.10 seconds for 63 files and 1,065 tests; and
the split layout at 44.41 seconds critical and 193.49 seconds summed.
The split improved the measured Vitest critical path by 8.9% and increased summed runner time by 4.1% because it added one job. Do not combine this comparison with the separate Jest baseline into a new precise Jest-versus-Vitest percentage.
Reducing Node or jsdom capacity was slower. Three Node or jsdom shards plus Browser measured 56.92 seconds. One Node shard plus two jsdom shards plus Browser measured 60.21 seconds.
Vitest hashes filenames rather than balancing by measured duration. Revisit duration-aware balancing only after persisted CI timing data exists. A speculative local sequencer was slower because test duration did not model import and setup cost.
vmThreads was also rejected. Native Vite and Rolldown configuration values do not cross its VM boundary and fail the Vite configuration suite.
Run Browser Mode once separately from Node/jsdom shards. Preserve the current Node 24/26 matrix during the remaining cleanup.
Revisit duration-aware sharding only after collecting persisted CI timing data.
Track the Vitest iframe failure only if it recurs. Do not mask it with a blanket retry.
Follow-up: further Browser Mode opportunities
After the migration, audit existing coverage for focused cases that would gain stronger or more direct evidence from Vitest Browser Mode. This investigation is separate from the Jest-to-Vitest cutover. Keep each test at the layer that best matches the behavior, and preserve coverage when moving it.
Review remaining *.jsdom.test.* files for assertions that depend on CSS, layout, geometry, scrolling, focus, observers, media queries or other real browser behavior.
Search test files for TODOs, skipped cases, mocks or comments that identify jsdom or browser limitations. Classify the actionable Browser Mode candidates.
Review existing Playwright E2E tests for component or integration behavior that Browser Mode could cover more directly. Keep complete user workflows in Playwright.
Review the Storybook test setup, including any E2E and visual-regression paths, to establish what they cover today. Identify overlap, gaps and cases where Browser Mode could replace or complement coverage without weakening visual regression.
Turn confirmed candidates into focused follow-up PRs grouped by package or behavior. Avoid broad mechanical suite migrations.
Follow-up: additional Vitest lint rules
The planned public Vitest configuration uses the @vitest/eslint-plugin recommended rules. Keep initial Vitest support opt-in while the public default still serves Jest; switch that default with the documented consumer migration. Do not enable configs.all or make every optional rule a completion requirement. Audit the remaining rules separately and enable only those that fit Gutenberg and public consumers.
Test the non-recommended rules against the current Vitest suites. Classify the findings and check for overlap with the existing Vitest convention validators.
Add broadly useful, low-noise rules to packages/eslint-plugin/configs/test-unit.js.
Add rules that enforce only Gutenberg repository conventions to tools/eslint/config.mjs.
Record why noisy, conflicting or preference-only rules remain disabled.
Implement selected rules and any required test cleanup in focused follow-up PRs. Do not add blanket suppressions.
Follow-up: Vitest 5 compatibility and optional improvements
The Node prerequisite landed in #82370, and Vitest 5 landed in #82687. Do not create another upgrade PR. Carry its matcher, setup and dependency changes into every planned PR and the published tooling.
Track the known Storybook peer-range mismatch. Trunk's @storybook/addon-vitest 10.5.9 still declares Vitest 3/4 support; storybookjs/storybook#36221 remains open as of September 10. Test: Update Vitest to version 5 #82687 reported passing Storybook coverage with the unpatched addon. Recheck the warning and smoke coverage after rebasing, then adopt a compatible upstream release when available.
Keep public Node/Vite engine and peer checks, synchronous/asynchronous matcher types and packed consumers in package-release verification; repeat consumer checks after publication and before adoption.
Evaluate shared Vite servers, filesystem caching, traces and reports separately after the cutover. Test: Update Vitest to version 5 #82687 did not find a reproducible overall speed improvement. Preserve isolation and complete test discovery when experimenting.
Reassess the load-sensitive Jest-console subprocess timeout only while that transitional probe still exists; the final retirement stage removes the retired package and probe.
Follow-up: intra-file order independence
#82212 shuffles Vitest files with the fixed seed 80855. It does not shuffle tests inside each file. The earlier test-level shuffling audit exposed order dependencies in 30 existing suites, including one-time warning assertions, shared registrations, and mutable fixtures. Keep this cleanup after the suite migration so it does not turn #82212 back into a mixed infrastructure and application-repair PR.
Refresh the remaining order-dependent suite inventory, then repair it with suite-owned setup and cleanup. Do not weaken the shared isolation defaults.
Enable deterministic test-level shuffling after those repairs pass.
Add a small set of fixed seeds only when each extra seed has demonstrated useful coverage at acceptable CI cost.
Follow-up: test log hygiene
Make successful unit and Browser Mode runs quiet enough that new problems remain visible. Fix root causes or use narrow, restored local assertions and mocks. Do not add broad global filters.
Record and classify warnings and expected errors from a full local and CI run.
Reach a clean passing log, or document each message that cannot be removed.
Follow-up: production type declarations
Keep the runner migration separate from existing declaration gaps:
publish accurate @wordpress/block-editor declarations for the store descriptor and BlockPreview;
add accurate declarations for affected @wordpress/interface compound components;
type Components style assertions with CSS custom properties without narrowing them to Partial<CSSStyleDeclaration>; and
remove temporary local suppressions when the owning production declarations are fixed.
Out of scope
Playwright E2E, PHP tests, historical changelogs, intentional legacy-result fixtures, and unrelated transitive Jest dependencies remain outside this migration.
Context
The @WordPress/gutenberg-components team is leading this migration. The main motivation is Vitest Browser Mode, which lets focused unit and integration tests run in a real browser. Component tests can check CSS, layout, element sizing, focus, events, observers, and other browser APIs without broad jsdom mocks. jsdom stays available for deterministic DOM tests. We are particularly excited about what this enables for UI component testing.
Vitest has been rapidly becoming one of the leading modern JavaScript test runners. Its ESM-first, Vite-native, and Jest-compatible design fits the modern JavaScript ecosystem, aligns with the current Storybook build pipeline, and makes an incremental migration practical. Earlier public discussions had already identified browser-based component testing as a useful direction, while #80738 showed the maintenance cost of Jest's ESM limitations.
The main gains are real-browser coverage, modern ESM support, explicit test environments, and a more maintainable toolchain. The historical measurements below describe the performance tradeoff; they do not establish an overall speed improvement over Jest.
What changes in practice
For most people working in Gutenberg, very little changes day to day. The existing
npm testandnpm run test:unitcommands remain, and most tests keep the same overall structure because Vitest provides Jest-compatible APIs. The visible changes are:*.jsdom.test.*for jsdom and*.browser.test.*for Browser Mode. Repository checks enforce the convention for new and changed tests.Most changes affect test tooling. The
@wordpress/domvisibility fix landed in #82574, the generated-style injection fix in@wordpress/buildlanded in #82154, and #82687 also fixeduseResizeObservercleanup on unmount. Preserve their regression coverage while finishing the migration.Consumers of WordPress test tooling do have a package-level migration. Existing npm releases remain available, but Gutenberg will stop publishing updates to
@wordpress/jest-preset-defaultand@wordpress/jest-console. Their replacements are@wordpress/vitest-preset-defaultand@wordpress/vitest-console, and@wordpress/scripts test-unit-jswill run Vitest. Publishing the new packages is additive. Switching existing public commands and defaults requires a documented tooling major release.Goal
Move Gutenberg-owned JavaScript unit and integration tests, plus published WordPress test tooling, from Jest to Vitest. Preserve test behavior and CI coverage, then remove active Jest runner infrastructure. Trunk already uses Vitest 5 and Node 24. Finish the remaining tooling cutover and cleanup against that baseline.
Vitest uses Node by default. Tests that need deterministic DOM semantics use
*.jsdom.test.*. Tests that need real CSS, layout, browser APIs, or browser interaction use*.browser.test.*and Vitest Browser Mode in Chromium.Current status
Reassessed on September 11, 2026 through #82761. The repository-owned test migration and the first cleanup are complete. The tracking issue remains open for the public tooling cutover and removal of active Jest infrastructure.
The foundation, repository migration, and first cleanup are merged:
The latest work on trunk is:
>=24.18.0and npm>=11.16.0; unit-test CI covers Node 24 and 26.WP_TESTS_SKIP_STYLE_INJECTIONflag did not land.vitest-browser-react, removes synthetic visibility and geometry setup, and adds one unsharded Chromium job.block.jsoninvalidation coverage and supported public Jest tooling.Current trunk routes all 1,145 discovered test files to Vitest and zero to Jest. The #82761 verification passed all 97 Browser files and 1,315 tests. CI still runs four Node/jsdom shards for each of Node 24 and Node 26, plus one Browser job on Node 24.
The overall migration is not complete.
npm run test:unitstill launches Jest throughwp-scripts test-unit-js, although the empty allowlist now makes it exit successfully with no tests.@wordpress/jest-preset-default,@wordpress/jest-console, the remaining repository Jest configuration, and compatibility probes also remain. The planned@wordpress/vitest-preset-defaultand@wordpress/vitest-consolepackages have not been added or published. The remaining work is organized into three PRs below.Next PRs, in execution order
These start after #82761. Each numbered item is one PR. Open these as drafts and base each dependent PR on its predecessor until that predecessor merges. Reuse existing implementation and fixtures where they still fit current trunk. Publication is a release gate within this sequence, not an additional PR or a reason to delay preparing later changes.
Add the public Vitest packages and prepare their additive release.
Add
@wordpress/vitest-consoleand@wordpress/vitest-preset-defaultfor Vitest 5, with package exports, accurate synchronous/asynchronous matcher types, public engine/peer ranges, package registration, changelogs and migration documentation. Use Node by default, suffix-selected jsdom and Chromium projects, CSS-module proxies in Node/jsdom, and real styles in Browser Mode. Preserve explicit imports, isolation and suite-owned browser mocks. Any public Vitest lint configuration introduced here must be opt-in; existing Jest lint defaults andwp-scripts test-unit-jsbehavior stay available.Verify packed packages in isolated consumers across the supported public Node/Vite combinations, including JSX/Emotion compilation, console matchers, config/setup behavior, failure output and native browser values. Cover both rebuilt CSS-module and regular-CSS output from
@wordpress/build, using its merged browser-safe guard. Confirm existing Jest tooling still works. Refresh dependency/security audits and release metadata.Release gate after this PR: publish and verify the two new packages before adopting them in released public tooling. Confirm the required
@wordpress/buildfix is also available in a published version and rebuild affected generated CSS. The source fix already landed in Build: Fix regular CSS injection in Vitest Browser Mode #82154; no additional build-fix PR is planned. Publishing these packages does not require switchingwp-scriptsor removing Jest.Adopt the published packages and switch the supported test tooling.
Use the published replacements in Gutenberg's shared test setup and
@wordpress/scriptsintegration. Verify actual npm artifacts in isolated consumers and record their versions and public support ranges. Makewp-scripts test-unit-jsdiscover consumer Vitest/Vite configuration and otherwise use the WordPress preset. Switch the relevant public lint defaults, update migration guidance and release metadata, and verify the default, watch, debug and update commands. Converge repository commands sonpm testruns the Vitest suite once rather than invoking it through two aliases.Test supported public Node/Vite combinations, config discovery, explicit jsdom/Browser opt-in and generated CSS behavior. Preserve the current Node 24/26 CI matrix, the single Chromium job, timezone coverage and Storybook smoke coverage. Make intentional public command/default changes at a documented major release boundary. Define the removal boundary for any deliberately retained Jest compatibility command; do not ask consumers to adopt a temporary command that immediately disappears.
Release gate after this PR: publish the consumer switch and verify the supported consumers against that release before final Jest retirement. Later implementation can be prepared in advance, but the replacement must be available before its compatibility paths are removed.
Remove the remaining active Jest infrastructure.
After the replacement tooling has shipped and supported in-repository consumers have migrated, remove obsolete Jest package sources, direct dependencies, commands, configuration, transforms, reporters, types, lint rules, compatibility probes and migration metadata. Honor any public removal boundary documented in PR 2. Regenerate the lockfile without reintroducing the removed Storybook runner or
@ariakit/testdependencies.Keep a permanent no-Jest CI audit and direct filename-based project-discovery tests. Prove that each test is discovered once, default commands execute Vitest once, and no supported developer or CI command launches Jest, including subprocess probes. Preserve fixed-seed file shuffling,
@testing-library/jest-dom, the runner-neutraltoMatchDiffSnapshothelper, React/SWC/Emotion compilation and local helpers that still have consumers. Remove obsolete Emotion style-snapshot types and dependencies. Run the complete current CI matrix, timezone tests, Storybook build/discovery/smoke coverage, build, typecheck and relevant lint.The package-release and consumer-adoption gates replace a single coordinated cutover. There is no requirement to wait for every external project to upgrade: document the supported migration boundary, verify representative consumers, and leave existing npm releases available. Optional follow-ups below are outside these three PRs and must not expand their scope.
Verification and release gates
block.jsoninvalidation and supported public Jest tooling.@wordpress/buildbehavior. Preserve the merged browser-safe guard instead of revivingWP_TESTS_SKIP_STYLE_INJECTION.wp-scriptsand other consumers. Record the published versions and support boundaries in the adoption PR.toMatchDiffSnapshotand@testing-library/jest-dom.The manual package workflow and RC1 package publication use the protected
WordPress packagesenvironment; latest publication shares thenpm-publish-wp-latestconcurrency group. Use those existing controls to publish the intended stage. An additive Vitest-package release is allowed before the consumer switch and Jest retirement. Verify the published dependency chain@wordpress/vitest-console->@wordpress/vitest-preset-defaultbefore releasing the@wordpress/scriptsversion that adopts it.The historical implementation and performance results below remain useful background. They are not completed checks for the three remaining PRs. The measured Jest-to-Vitest performance tradeoff was accepted previously; no overall speed improvement is claimed.
Architecture and test conventions
addedorretiredbookkeeping are not part of the contract.*.browser.test.*to Browser Mode,*.jsdom.test.*to jsdom, and all remaining tests to Node.wp-buildguard istypeof process === 'undefined' || process.env.NODE_ENV !== 'test'. Node/jsdom skip automatic injection in test mode; Browser Mode injects because it does not defineprocess. Regular CSS also checks thatdocumentexists. There is noWP_TESTS_SKIP_STYLE_INJECTIONoverride.@wordpress/buildfixtures to verify both generated output paths in isolated jsdom and Browser Mode consumers.vitest-browser-react; jsdom React tests use Testing Library React. Shared DOM matchers and permitted query utilities remain. Preserve the merged renderer convention checks.userEvent. Four documentedfireEventexceptions remain where synthetic event construction is the behavior under test.Merged guardrails and rebase notes
#81040 merged on August 27, 2026. It adds live exact-one-runner routing, explicit environment policy, direct workspace dependency checks, ESM and explicit Vitest import rules, TypeScript test graph validation, stale-exception checks, and empty Jest shard support.
The policy distinguishes deterministic jsdom behavior from browser-dependent layout and style behavior. Its binding analysis covers Vitest and Testing Library aliases, namespaces,
screen,render,within, collection queries, callback aliases, object members, bound callbacks, and type-only imports. The merged CI run passed Static Analysis, build, routing, and all four JavaScript unit-test shards.#81918 removed the Jest-based Storybook runner in parallel. Preserve that cleanup when regenerating the remaining stack lockfiles; do not restore its obsolete transitive dependencies. Rerun the no-Jest invariant and Storybook smoke checks at the final head.
The September 10 source reassessment found
@swc/plugin-emotion16.0.0 and@swc/core1.16.2 on trunk after #82660. Preserve those updates when refreshing the older public preset and rerun packed consumers at the final head.#80991 is a mechanical classification step. It renames DOM-dependent tests to
*.jsdom.test.*, preserves*.browser.test.*, and leaves Node-compatible tests without an environment suffix. It does not migrate Jest APIs.Historical final-head verification snapshot
This snapshot predates the merged Storybook cleanup and the current three-PR remainder above. Treat it as historical evidence, not current exact-head verification.
At final stack head
ad994712aa11cf1bf612b33facbd0bb535be5995:npm ci --ignore-scripts;The final cleanup removes active Jest packages, runner configuration, transforms, types, rules, the migration manifest, stale
@ariakit/testentries, and duplicate root test invocation. Historical changelogs, intentional legacy-result fixtures, runner-neutral@testing-library/jest-dom, and unrelated transitive Jest dependencies remain out of scope.Dependency, compatibility, and release boundary
At the September 10 trunk snapshot, the lockfile resolves Vite 8.2.2, Vitest and
@vitest/browser-playwright5.0.0,@swc/core1.16.2,@swc/plugin-emotion16.0.0, Playwright 1.63.0,vitest-browser-react2.3.0, Testing Library React 16.3.3, jest-dom 7.0.1 and Flakiness.io Vitest 1.9.2. Refresh the remaining branches without restoring their older resolutions.The proposed public preset supports Vite 7 or 8. Validate that range with Vitest 5 and the supported public Node versions before publishing; adjust the declared range if a combination is not supported.
jsdom stays at 26.1.0. A jsdom major upgrade is separate work and is not required for this runner migration.
The historical local lockfile audit reported 76 repository advisories: 6 low, 41 moderate, 28 high, and 1 critical. The migration did not add a specific advisory. Refresh this count at the final rebased head.
The internal test migration, JSX renames and environment classification preserve production behavior. Keep focused compatibility checks for the
@wordpress/domvisibility fix and the@wordpress/buildgenerated-style contract.Changing
@wordpress/vitest-preset-defaultto use Node by default is intentional. Consumers must explicitly opt into jsdom or Browser Mode. Publishing the new packages is additive. Changes to the@wordpress/scriptsrunner, public Node support and ESLint defaults need their own documented major release boundary. Retire Jest support only after the replacement ships and its supported consumers are verified. The repository npm minimum already increased to 11.16.0. Published tooling has its own engine contract; verify it explicitly. Production packages are not being converted wholesale to ESM.Migration size
The historical sum of the 12 migration PRs against their intended parents was 21,662 added lines and 20,474 removed lines. The aggregate text churn was 42,136 lines, with a net increase of 1,188 lines.
package-lock.jsonaccounted for 1,668 additions and 2,099 deletions. Excluding repeated lockfile churn, the migration added 19,994 lines and removed 18,375 lines, with a net increase of 1,619 lines.This excluded the parallel #81918 cleanup and did not compare the final head with moving
trunk. Refresh these figures only if they remain useful after the final rebase.Performance measurements and CI layout
Current measurements do not show an overall speed improvement over Jest. The environment split improves Vitest's critical path compared with mixed sharding.
The pinned Vite 8 Jest baseline and the earlier mixed-shard Vitest layout used Node 20.20.2 on the same machine. Dependencies, repository patches, and generated parser files were prepared outside the timed section. Each result is the median of three complete rounds with four fresh-process shards and 11 workers per shard.
Before the thread-pool and setup-scope optimization, Vitest measured 52.36 seconds on the critical path and 195.18 seconds summed. The optimization reduced those results by 21.5% and 19.3%. A matched Node or jsdom comparison measured a further 17.0% critical-path improvement from threads instead of forks.
The historical layout below used four Node/jsdom shards plus one Browser job. Current trunk runs those four shards on both Node 24 and Node 26, plus one Node 24 Browser job. Preserve this matrix, current required check names and the final invariant job. Chromium installation and Browser Mode initialization occur once.
A same-head comparison at
ad994712aa11cf1bf612b33facbd0bb535be5995measured:The split improved the measured Vitest critical path by 8.9% and increased summed runner time by 4.1% because it added one job. Do not combine this comparison with the separate Jest baseline into a new precise Jest-versus-Vitest percentage.
Reducing Node or jsdom capacity was slower. Three Node or jsdom shards plus Browser measured 56.92 seconds. One Node shard plus two jsdom shards plus Browser measured 60.21 seconds.
Vitest hashes filenames rather than balancing by measured duration. Revisit duration-aware balancing only after persisted CI timing data exists. A speculative local sequencer was slower because test duration did not model import and setup cost.
vmThreadswas also rejected. Native Vite and Rolldown configuration values do not cross its VM boundary and fail the Vite configuration suite.Follow-up: further Browser Mode opportunities
After the migration, audit existing coverage for focused cases that would gain stronger or more direct evidence from Vitest Browser Mode. This investigation is separate from the Jest-to-Vitest cutover. Keep each test at the layer that best matches the behavior, and preserve coverage when moving it.
*.jsdom.test.*files for assertions that depend on CSS, layout, geometry, scrolling, focus, observers, media queries or other real browser behavior.Follow-up: additional Vitest lint rules
The planned public Vitest configuration uses the
@vitest/eslint-pluginrecommended rules. Keep initial Vitest support opt-in while the public default still serves Jest; switch that default with the documented consumer migration. Do not enableconfigs.allor make every optional rule a completion requirement. Audit the remaining rules separately and enable only those that fit Gutenberg and public consumers.packages/eslint-plugin/configs/test-unit.js.tools/eslint/config.mjs.Follow-up: Vitest 5 compatibility and optional improvements
The Node prerequisite landed in #82370, and Vitest 5 landed in #82687. Do not create another upgrade PR. Carry its matcher, setup and dependency changes into every planned PR and the published tooling.
@storybook/addon-vitest10.5.9 still declares Vitest 3/4 support; storybookjs/storybook#36221 remains open as of September 10. Test: Update Vitest to version 5 #82687 reported passing Storybook coverage with the unpatched addon. Recheck the warning and smoke coverage after rebasing, then adopt a compatible upstream release when available.Follow-up: intra-file order independence
#82212 shuffles Vitest files with the fixed seed
80855. It does not shuffle tests inside each file. The earlier test-level shuffling audit exposed order dependencies in 30 existing suites, including one-time warning assertions, shared registrations, and mutable fixtures. Keep this cleanup after the suite migration so it does not turn #82212 back into a mixed infrastructure and application-repair PR.Follow-up: test log hygiene
Make successful unit and Browser Mode runs quiet enough that new problems remain visible. Fix root causes or use narrow, restored local assertions and mocks. Do not add broad global filters.
Follow-up: production type declarations
Keep the runner migration separate from existing declaration gaps:
@wordpress/block-editordeclarations for the store descriptor andBlockPreview;@wordpress/interfacecompound components;Partial<CSSStyleDeclaration>; andOut of scope
Playwright E2E, PHP tests, historical changelogs, intentional legacy-result fixtures, and unrelated transitive Jest dependencies remain outside this migration.