Repository navigation
fix(browser): wait for setup mocks before importing a test file - #11520
Merged
sheremet-va merged 1 commit intoOct 7, 2026
Merged
Conversation
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
sheremet-va
approved these changes
Oct 7, 2026
kasperpeulen
added a commit
to storybookjs/vitest-plugin-rsc
that referenced
this pull request
Oct 7, 2026
Vitest 5.0.3 leaves the config of an environment that the browser consumes alone (vitest-dev/vitest#11378), so the plugin no longer saves and restores the optimizer of its module runner environments around Vitest's hook. Its tests, which stood in for the old Vitest, go with it. The patch of @vitest/browser moves to 5.0.3: the fix for vi.mock in a setup file (vitest-dev/vitest#11520) is merged but not released. BREAKING CHANGE: the plugin needs Vitest 5.0.3 or later. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
kasperpeulen
added a commit
to storybookjs/vitest-plugin-rsc
that referenced
this pull request
Oct 8, 2026
* feat: run whole Next.js routes with visit()
Adds `vitest-plugin-rsc/next`, an experimental entry that runs a Next.js
App Router app in the test's browser tab: Next's own request handler, the
HTML it renders, and Next's own client entry, which hydrates it.
Next's three layers (rsc, ssr, browser) each run as a Vite environment,
with the aliases, constants and React that Next's build gives that layer.
The build is done by Vite; the route list, loader tree and request handler
come from Next's build code, and everything behind them is Next's runtime.
Needs next@16.4 or later.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: run the routes compat job against canary only
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: open Next.js routes with renderServer()
Replaces visit() in vitest-plugin-rsc/next. renderServer({ url }) opens a
route; renderServer(<Node />, { url }) renders a node where the route has
its page, inside its layouts. Both take request headers and resolve with
the server's response.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: harden renderServer() after review
Send a cookie header as given, put the node in the slot that has the
route's page, clear the tab's storage between tests, leave pages one at a
time, and take options whatever the argument count.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: serve Next.js route handlers through Next's edge entry
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: compile the app's server code as server code
The server layers of vitest-plugin-rsc/next run in a browser tab. Until now only Next's own files were compiled not to see it. Now every module of a server layer is: source files and pre-bundled dependencies, in the ssr and the rsc layer.
- window, document, location, localStorage and sessionStorage are declared as variables of the module, so typeof window is "undefined" and the text of a function stays what it was.
- fetch, Request and Response are the server's, so a fetch with Next's cache options goes through Next.
- In the rsc layer's environment, which the test shares, test files and setup files (from Vitest's config), Vitest's own packages and the new testModules option of vitestPluginNext() keep the tab.
Also resolves Next's aliases from the project instead of from the importer, so a package that does not depend on next gets the project's copy.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: make vitest-plugin-rsc/next hold up on a full app
Found while moving the notes demo to the new entry:
- Resolve the files of `next` that an alias points to from the project, not
from the importer. A package like next-themes can see no `next` from where
pnpm installs it, or another copy, and then renders with a second React.
- Give the layers that load through a module runner the page's Vite client.
A copy of their own opened a websocket per page load to the configured
port, which can be another dev server, and reloaded the tab when it closed.
- Load next.config.ts from the project directory, one project at a time: Next
resolves its relative imports from the working directory.
- Leave a Server Action id that names no action out of the manifest, so that
Next answers it with its unrecognized-action response instead of a 500.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: run the notes demo on next@16.4 and vitest-plugin-rsc/next
The page tests open their route with renderServer({ url }), through the real
RootLayout. The probe tests render their probe in place of the page of a
route under app/fixtures. Tests of the old helpers' internals are gone, and
the cache tests that need Next's Data Cache are skipped until it works on the
new entry.
CI runs the demo against the pinned next@16.4 and canary.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: hide the tab from dependencies the way Next's build does
After review of the server-code change.
- A pre-bundled dependency gets typeof window replaced, not a variable declared: Rolldown renames clashing variables across the modules of a chunk, inside functions too, which changed the text of a function. Source files keep the declaration.
- A dependency that does not parse as JavaScript is pre-bundled as it is, with a warning, instead of failing the pre-bundle.
- Test files are matched with test.include and test.exclude directly, so a file with in-source tests stays server code. Every project that shares the plugin adds its own.
- The packages of Vitest's scope that are installed count as the test runner: the browser provider, coverage.
- globalThis.window is no longer replaced: an assignment to it compiled to an assignment to undefined.
- Only JavaScript and TypeScript source files are compiled, and errors in them surface.
- The cache of pre-bundled dependencies is keyed on testModules through a define, not through the plugin's name.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: harden route handlers after review
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: apply a setup file's vi.mock() before a test file's imports
In browser mode, Vitest 5.0.0-beta.7 to 5.0.3 imports a test file without
waiting for the mocks a setup file asked for (vitest-dev/vitest#11450). A
test file with no vi.mock() of its own then gets the module unmocked.
vitestPluginRSC() now registers a last setup file that waits for them, so
a bare vi.mock() in the project's setup file is enough.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep source maps of compiled dependencies and guard the test file rule
After the second review of the server-code change.
- The optimizer plugin passes on the source map of its pass instead of dropping it.
- Without Vitest's config, source files of the rsc layer are left alone: a test file cannot be told from a file of the app then.
- A route entry is compiled in one pass, with the constants of its layer.
- The warning for a dependency that does not parse says why.
- Docs: what happens to fetch in the text of a function, how to write a pattern for a package, which packages need testModules, where Next patches fetch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: register the mocks setup file after those of other plugins
From review: a post plugin, so setup files that other plugins add come
before it. Say what the fix needs and where the Vitest report stands.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: keep Next's Data Cache across requests and reset it between tests
unstable_cache and a cached fetch now keep their result for the next request
on vitest-plugin-rsc/next: the server shares one IncrementalCache with the
pages and the route handlers, the way next start shares its cache with an edge
function. cleanup() empties it.
Inside a request, an AsyncLocalStorage run() now ends when its callback
returns instead of when its promise settles. The store of a cache scope no
longer reaches the components that render while a cached function runs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: patch Vitest instead of working around it in the plugin
The late setup-file mock is Vitest's bug, not something this package
should hide. Patch @vitest/browser in this repo so its own demos can
mock app modules in a setup file with isolate: false, until the fix is
in Vitest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat!: rename testModules to browserModules
The option is for modules that have to see the browser they really run in, which is not the same as being part of the test. A package asks typeof window either for its role (am I the server side of the app?) or for its capability (is there a DOM here?), and only the second kind belongs in the list.
Adds @t3-oss/env-core to the demo as the role case: a Server Component reads a server variable, which the library refuses in a browser.
Docs say when the option is needed, from running the notes demo (PGlite, MSW, Drizzle, @t3-oss/env-nextjs) without any entry.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: say when browserModules is needed, as far as it was run
After the review of the rename.
- A capability check that also works without a DOM, like PGlite's, does not need an entry.
- Test files and Vitest's packages get the browser's answer without being listed.
- An entry does not help a package that takes a Node-only path on a server.
- Unit test for a package pattern against a path in a package manager's store.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: apply review of the Data Cache change
Keep the store of a Server Action left for the render after it, so a
redirect() in a component replaces the page. Let Next pick its own cache
handler, which keeps its 2 MB limit per entry. Reset the cache with the key
prefix alone. Pin the limits of a cache scope with tests, and correct the docs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: harden the Server Action lookup and use one Vite client
After review of the fixes for a full app:
- The page's Vite client reaches the other layers through a static import of
the URL Vite itself writes, so it cannot become a second instance. The
exports it forwards are read from Vite's client file.
- An id of a request counts as a Server Action only for a POST, when it is
shaped like an id and names a server reference. When the module of the
action fails to load, Next reports that error instead of "no such action".
- The alias fix reads like the one of the route handler and server code
branches, and the working directory is restored to the one of the process.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: tighten the notes demo after review
- The skipped cache tests say what each fails on, and name the pull request
that un-skips them.
- The stale-tree tests wait for the whole response of the action, and the
catch-all route has its test without extra segments again.
- console.error is a spy per test, as in next-e2e-demo.
- The demo's own config runs four tabs at once: with a tab per core the first
test of a file timed out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: update @vitejs/plugin-rsc to 0.5.36
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat!: run Next.js App Router apps in the test's tab
`vitestPluginNext()` runs a Next.js App Router app in the browser tab of a
Vitest Browser Mode test. Next's own request handler renders a route, the
tab shows the HTML, and Next's own client code hydrates it. Server Actions,
route handlers, cookies, redirects and the Data Cache go through Next's
runtime, for next@16.4 and later.
- `vitest-plugin-rsc/nextjs` exports `renderServer`, `handleRequest` and
`cleanup`; `vitest-plugin-rsc/nextjs/plugin` exports `vitestPluginNext`.
- Peer dependencies: `next >=16.4.0-0` (optional) and `vitest >=5.0.0`.
`msw` is not a peer dependency.
- `playground/nextjs-e2e-demo` is the small app the feature tests run
against; `playground/nextjs-notes-demo` is the acceptance app.
- CI runs every project against the pinned Next.js, and the two Next.js
playgrounds against `next@canary`.
BREAKING CHANGE: `vitest-plugin-rsc/nextjs` exports `renderServer({ url, headers })`, `renderServer(<Node />, { url, headers })`, `handleRequest(input, init)` and `cleanup()`, and `vitest-plugin-rsc/nextjs/plugin` exports `vitestPluginNext({ browserModules })`. Both need next@16.4 or later; no other Next.js helper or option is exported, and `vitest-plugin-rsc/nextjs/client` is now an internal module of the plugin with other exports, so an import of it resolves without an error but to another module. The package exports `vitest-plugin-rsc/async-local-storage` and the `./*` catch-all are removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: let the notes demo's theme timer run before a test leaves the page
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: release the slow report from the test instead of on a timer
The test left the page while a report was being written, and gave the
report 750 ms for that. A slow machine hydrates the page later than that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: load the whole document before the app starts
The page went into the document while the server was still sending it, and
Next's client started half way. Next's client reads the Flight payload until
the document has loaded, so `document.readyState` had to say "loading" until
the end, and the plugin dispatched `DOMContentLoaded` itself.
Now `renderServer()` reads the whole response, moves it into the document and
runs its inline scripts, and then starts the app. The document has loaded when
Next's client looks, so neither is faked. A script that throws is reported and
the page goes on, as in a browser.
The first load of a page now waits for all of its data, so it does not show a
`loading.tsx` or a Suspense fallback. A navigation in the app still does; the
e2e demo tests that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: handle the left page's rejection before the test leaves it
The test attached its expectation to renderServer() only after cleanup() had
rejected it, which the browser reports as an unhandled rejection.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep two Vitest runs on one machine apart (#63)
A module runner in the page evaluated its own copy of Vite's client, which falls back to the configured port: another run's server when that port was taken. Every environment the page runs through a module runner now gets the page's own Vite client. Removes createBrowserApiPortPlugin, which never ran, and the server.hmr fields of the module runner's socket, which Vitest always leaves unset.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: list routes and build loader trees the way next build does
The app loader got no explicitParallelRouteChildren, strictRouteMatching and isFinalRouteMatcher, which next build passes from the resolved config, so a layout of slots only got an implicit children slot and answered 404. The routes are now grouped with Next's normalizeCatchAllRoutes, compareAppPaths and selectAppPageEntry, as createEntrypoints does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor!: tell server code it is on a server the way Next does, with typeof only
Source files of the server layers got the tab's globals declared as unassigned variables, so that window.innerWidth threw. Next's own server build only replaces typeof window. Source files now get the same typeof replacement as pre-bundled dependencies, and the declaration with its helpers is gone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: name route modules by index and merge overlapping demo tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: use plugin-rsc's static prerender and Next's own config type
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: make project.ts the one place that calls Next's build code, with a contract check
project.ts loads every build module of the installed Next through one function, typed against Next's own declarations, and fails with the Next version and the assumption that no longer holds: a file or export that is gone, discoverRoutes() without mappedAppPages, a missing NEXT_RUNTIME define, a missing Flight alias, IncrementalCache options that are ignored, and the pieces of generated code the plugin replaces.
The replacements moved from plugin.ts into project.ts, so they are checked when a run starts. The route entry now imports app-page-entrypoint directly, which drops a resolve plugin and stops pre-bundling Next's Node.js request handler. The resolver no longer strips .compiled, and uses plugin-rsc's directive transform.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: pipe a response body with the platform's pipeTo
finishWithBody pumped the body by hand to know when the server had written it and to stop it. A TransformStream without a limit and pipeTo with an abort signal do the same. The directory of this package is now read off import.meta.url instead of searched for.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: run the Next.js playgrounds against next@latest as well as canary
Also documents what is checked of the installed Next.js when a run starts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: report a page left while its document arrives as left
Since the body is piped with pipeTo, leaving the page errors the document
instead of cutting it short, so renderServer() rejected with "Failed to
fetch" instead of saying the page was left.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: list a route by the file Next builds for it, and reject the routes next build rejects
A catch-all page of a slot is listed with every route it also matches. The
kind of a route and the page-or-route-handler check went over that whole list,
so Next's modal pattern next to a route handler failed with "is both a page and
a route handler", and next to `sitemap.ts` with a false "Next.js differs"
message. The kind is now that of the file Next selects, and the check is over
the route's own files.
The routes `next build` rejects are rejected with Next's own errors, now also
an interception route without the route it intercepts (`strictRouteMatching`,
on by default).
Server code that asks `typeof(window)` is told it is on a server as well, and
a build module that does not load keeps its error as the cause.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep the tab when the app navigates to another origin
A navigation to another origin, like a sign-in or a checkout, was let
through. location.assign() took the tab with it, and with the tab the test
file: "Cannot connect to the iframe". A server redirect there was fetched
from the network, which failed with "Failed to fetch". Both are now an
error that names the URL, and the tab stays.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep the Set-Cookie of Response.json() on the server
The server's Response is a subclass that keeps set-cookie, but its static
json() was the browser's, which builds a browser Response. A route handler
that answered Response.json(data, { headers }) or NextResponse.json(data,
{ headers }) lost its cookies. json() now lets the browser check and build
the response, then makes it one of the subclass. Like the platform's, it
does so also when a subclass calls it. redirect() takes no headers to drop
and stays the browser's.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: leave the page when a Client Component makes a React root of its own
To find the root Next creates, hydrateRoot() and createRoot() were patched
while the app started, and the last root made won. A Client Component that
made a root of its own in an effect took its place: leaving the page
unmounted that root, and the app went on running. Only the root of the
document is Next's. If Next makes none, starting the page is an error that
says so; the app it started then runs on, as there is no root to unmount.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: reject a page load the test leaves before the server responds
A page that waits for its data without Suspense sends nothing until it has
it. A load of such a page that the test left waited for the server all the
same: forever if the data never came. Once it came, the render went on
without its request and failed with an InvariantError of Next, which the
load passed on as an uncaught error. A request from the tab to the server
now rejects as soon as its signal aborts, as fetch does, and nobody reads
the answer that comes after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: install the Next.js release that latest and canary name today
pnpm 11 waits a day before it installs a new release, so next@latest was
16.3.8 on the day 16.4.0 came out, which the plugin does not support.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore!: update Vitest to 5.0.3 and drop the optimizer round trip
Vitest 5.0.3 leaves the config of an environment that the browser consumes
alone (vitest-dev/vitest#11378), so the plugin no longer saves and restores
the optimizer of its module runner environments around Vitest's hook. Its
tests, which stood in for the old Vitest, go with it.
The patch of @vitest/browser moves to 5.0.3: the fix for vi.mock in a setup
file (vitest-dev/vitest#11520) is merged but not released.
BREAKING CHANGE: the plugin needs Vitest 5.0.3 or later.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: check at startup what the tab assumes of Next's runtime
project.ts now also checks the runtime that the plugin's modules call in the tab, with the same message: the Next version and what differs. It checks what would fail silently, or without saying why: hydrate() of the client entry, which client.tsx imports dynamically; Next's __NEXT_HYDRATED_CB hook under __NEXT_TEST_MODE; the document.currentScript asset prefix; the exports of server-reference-info that the shim reads and replaces; the globals self.__BUILD_MANIFEST, self.__SERVER_FILES_MANIFEST and self.__RSC_MANIFEST that the edge route module reads; and the options setManifestsSingleton() takes. A static import of a name that is gone needs no check: its module fails to link with a SyntaxError that names it.
Those files run in the tab, so they are read and parsed with Vite's parseAst, not loaded. An export is found also when it is exported apart from its declaration, re-exported or comes through export *, and the options are found destructured or read off the parameter. Each check has a test that fakes the change, and the other forms of setManifestsSingleton() have tests that they pass.
The server-reference-info shim re-exports the rest of the module with export *, so an export that Next adds is not lost.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: give the rsc layer every export of Next's Flight codec
The bridge modules that hand Next's react-server-dom-webpack imports to Vite RSC's codec in the rsc layer listed their exports by hand, and shimMissingExports turned a name that was not on the list into undefined. Now project.ts reads the exports of the codec entries that Next's rsc aliases point to, with cjs-module-lexer, which is how Node finds the named exports of a CommonJS module, and the bridges have all of them. One that rsc.ts has an adapter for is forwarded to it. One it has none for throws when it is called, naming it and the Next version. A new export of the codec that nothing calls does not stop a run. flight.ts lists the adapted names and builds the bridge, and rsc.ts is typed against it.
shimMissingExports stays: a package of the app can import what only another layer's build of a module has, like react-transition-progress importing useRouter from next/navigation, which is navigation.react-server.js in the rsc layer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: cover one request at a time, and the Vite CSS that outlives a page
Two guards had no test that needs them. Each new test fails with its guard switched off.
Two requests sent at once get their own request stores: the route handler reads headers() after it has awaited, and without the queue in ssr.ts the first request reads the headers of the second.
The CSS of a page whose module loaded during a client navigation, while another page was there, still applies when a later page load opens that page: the module does not load again, so without isViteStyle the style is gone with the page it was added to. The e2e demo gets a /notice page with CSS of its own, linked from the layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: warn once at startup about the metadata files the app leaves out
A metadata file like app/icon.png, app/opengraph-image.png or app/sitemap.ts was silently an empty list in the loader tree, or a route that was not served. project.ts now lists the files that Next lists as metadata routes, and the plugin warns once with Vite's logger when a run starts, naming the app and the files. Not app/favicon.ico: it only adds a <link rel="icon">, and every create-next-app app has it, so a warning for it would teach people to ignore the warning.
A warning and not an error: the plugin stops a run for what next build rejects and for a Next.js it was not written for. Other features under Not Yet, like middleware.ts or next/font, do not stop a run either.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: let go of a page once the test has left it
Every renderServer() is a page load: a module graph of its own for React,
Next's router, Next's dev overlay and the app's client code. The tab never
reloads, so what a page left on the tab kept its whole graph: 7.75 MB per
page load, never freed (99 MB after 1 load, 1261 MB after 150).
The plugin now cleans up what it sets itself, and what React and Next leave
behind during the calls that the plugin makes:
- `__webpack_require__` of the browser layer is one function per page. React's
Flight client wraps a property of it when it loads, so the one that the
pages shared chained every page to the one before.
- The 5 s timer of leavePage() is cleared once the race is over.
- React adds listeners to `document` inside hydrateRoot() and createRoot(),
which the plugin already wraps. They are recorded for that one call and
removed after root.unmount().
- React's scheduler, the scheduler in Next's dev overlay and Next's router
open a MessageChannel or add a listener to `window` when their module
loads. Those are recorded while the plugin loads Next's client, and only
then, and removed or closed when the page is left. The modules of the app
wait until that is over, so nothing of the app is recorded.
What the app's own code or the test adds to the tab is not touched. The
recorder wraps the method that `window` has at that time, which is Vitest's:
it counts the error listeners of the test.
After: 99 MB after 1 load, 104 MB after 300. A page with a portal into
`document.body` is still kept: React adds its listeners to the body when the
portal renders, and the tab has one body.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat!: import the Next.js test API from vitest-plugin-rsc/nextjs/testing-library
`renderServer`, `handleRequest` and `cleanup` for a Next.js app now come
from `vitest-plugin-rsc/nextjs/testing-library`, like the ones for plain
React Server Components come from `vitest-plugin-rsc/testing-library`.
BREAKING CHANGE: `vitest-plugin-rsc/nextjs` is no longer an export. Import
from `vitest-plugin-rsc/nextjs/testing-library` instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat!: render a node on its own, in a container
`renderServer(<Node />, options)` renders one node the way Testing Library
renders a component: in a `<div>` in `document.body`, without the layouts
of the app. It resolves with `{ container, baseElement, asFragment,
unmount, response }` and takes `url`, `headers`, `wrapper`, `container`
and `baseElement`.
The node is the page of a route that exists while the node is there, at
the pathname of `url`. Its loader tree has the segments of the app's route
for that URL, so Next finds the same params, Next's builtin boundaries at
its root, and no layout. The tree goes into Next's `app-page` template and
is a page from there on: the same request handler, module lists, cookie
jar, cache and page load as a page of the app.
Next's client entry only hydrates `document`. The wrapper around
`hydrateRoot` and `createRoot` that keeps Next's root passes the container
in its place. Next's own `hydrate()` creates the router state and renders
its `AppRouter`; nothing of the router is made up.
The root segment of a node's tree is not the app's, which Next's router
takes for another root layout. So a navigation from a node to another
pathname is a page load of that route of the app.
A response that is not the node's fragment loads as the page it is: the
page a node redirects to, and the document Next sends for a node that
throws or calls `notFound()`.
`project.ts` checks at startup what this needs of Next: the injections of
the `app-page` template, the builtin boundary files, `appElement` in
`app-index.js`, and how `render-tree.js` and `segment-cache/cache.js`
decide on a page load.
Also: a request that is redirected more than 20 times fails with an error
that names the URL, for a node and for a page. A URL with a trailing slash
is served as it is, which is now under "Not Yet".
The fixture routes of the playgrounds are gone. Their tests render the
probes on their own.
BREAKING CHANGE: `renderServer(<Node />, { url })` no longer renders the
node in place of the page of a route, inside the layouts of that route.
Pass what the node needs around it as `wrapper`, or open the page with
`renderServer({ url })`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: compile the source files of the app with Next's SWC transform
Next's build compiles a source file before it bundles it. The plugin now
runs that transform, from project.ts, for the files of the app in each of
the three layers, with the options Next gives the layer.
- A mistake `next build` stops at, like a client hook in a Server
Component or `server-only` in a Client Component, fails the test with
Next's error: the module throws it when it loads.
- styled-jsx works, with the `styled-jsx` that Next depends on.
- `next/dynamic` with `ssr: false` leaves the import out of the server.
- `compiler` of next.config applies, like `removeConsole`.
- JSX in a `.js` file compiles, as in Next.
- `server-only` resolves in the client layers: an alias that leads
nowhere is skipped, as webpack does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: resolve the paths of tsconfig, as Next's build does
An import like `@/components/button` failed to resolve unless the Vitest
config turned on Vite's `resolve.tsconfigPaths`. The plugin turns it on,
unless the config sets it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: support next/font with Next's font loaders
A call of a `next/font/google` or `next/font/local` function did not work:
Next's build replaces it with an import of the font's CSS. The SWC
transform now does that, and the plugin loads the import with Next's
next-font-loader and css-loader, called from project.ts. The class names,
the CSS variable and the fallback font are the ones Next makes, and the
font files are served under `/_next/static/media/`.
A package that calls `next/font` itself, like `geist`, is compiled for it
when Vite pre-bundles it, as Next's build compiles such a package.
The notes demo no longer mocks `next/font/google`. Both playgrounds read
the answers of Google Fonts from `vitest.google-fonts.cjs`, through
Next's own NEXT_FONT_GOOGLE_MOCKED_RESPONSES, so their tests need no
network.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: support imported images and next/image with Next's loader and optimizer
`import logo from "./logo.png"` was a URL, which is Vite's, so
`<Image src={logo} />` failed for want of a width. It is now what Next's
next-image-loader makes of the file: `{ src, width, height, blurDataURL }`,
with the file served under `/_next/static/media/`.
The dev server answers `/_next/image` with Next's image optimizer, as
`next start` does, with the `images` of next.config: for an imported
image, a file in `public/`, and an image of a server that
`remotePatterns` allows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: say what Next's compiler does here, and what it does not
A section on the compiler in docs/next-routes.md, with what comes from
Next, what is Vite's, and what is checked of the installed Next. The
lists of what does not work name what Next's build does and the plugin
does not: a package that is not compiled, a `webpack` function, the React
Compiler, CommonJS source files, `.env` files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: stop the render of a page that was left as an abort, not as an error
Next reported the stopped render with console.error, twice. A browser that
leaves a page is an aborted request to a server, which Next does not report.
A test where the app navigates away while a response still streams failed on
it when the machine was slow.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: load the font itself, not the Arial that Next falls back to
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: end a cancelled response as an abort too
A response whose body the tab cancels reached Next without a reason, which
Next reports like any other error of the render.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: give a page a body of its own, so a portal does not keep it
React adds its listeners to the body when a portal renders there. The body
was the tab's, for every page, so each page that rendered a dialog stayed in
memory. A page now gets a new body, which goes with the page.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: give a node a body of its own too, so a portal does not keep it
A node rendered in the body of the test, where React adds its listeners when
a portal renders. The node now gets a new body, with what the test has in it,
and the test's body is back when the node is left.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: let the base element of a node be the body of the document, whichever that is
A node has a body of its own, and the page after it another, so a body that
was kept is not in the document for long.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: keep what the test gave the document, and say when a container is not in it
The attributes of <html> and <body> are the ones from before each page, not
from when the tab started. A script of the test's that is nested in the body
does not run again. A container that went with a page gets its own error.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: in watch mode, run the test files that opened the route of a file that changes
Every test file imports the module that lists the routes, so an edit of one
page ran them all. The tab now says which routes a test file loads, and the
plugin puts that in Vite's module graph right before Vitest looks up the test
files for a change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: keep Tailwind's scanned files out of watch mode in the notes demo
Tailwind registers every file it scans as a dependency of the stylesheet, and
Vitest's watcher follows that: a save of any file ran every test file whose
page has the stylesheet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: let vitest --changed and vitest related find the test files of a route
Vitest looks them up from the imports of a test file, in an environment
without Next's compiler, and a test file does not import the page it opens.
A run now writes down what each passing test file depends on, from Vite's
module graphs of the three layers, and the next lookup gives the test file
the changed files among them as imports. A test file that is not written
down runs for every change of the project.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: answer for a test file itself when Vitest looks up the tests of a change
What a test file depends on was written down without a way to tell that it
was out of date, and Vitest followed it by transforming files of the app
without Next's compiler. Each file now has a hash, and a test file belongs
to a change when one of its files is a changed one or is no longer what it
was. Also written down: the setup files, the mock of a module, the module
of a Server Action, and what Next reads next to the app directory. A test
file with a skipped module, a global name pattern or a bail is not.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: close the last ways a changed file could miss its test files
A run with --tags is a run in part. What next.config and tsconfig import is
read off their text, since Vite has no module of them. A mock that comes
after the run counts, and so do the mocks of packages in the root. The
empty test file of a lookup is let go of when a run starts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: drop the refresh of Next's redirect tag when a page loads
For a redirect() in a response that had started, Next sends a
<meta http-equiv="refresh"> for a browser without JavaScript, and its router
loads the page it redirects to when it finds that tag. A browser drops a
refresh that is still pending with the page. The test's document stays, so
the refresh came due a second later: in the page that was there by then,
which it replaced, or between two tests, where nothing stopped the tab from
leaving and the rest of the test file did not run.
The tag now goes into the document without its http-equiv: the router still
finds it by its id, and nothing is pending.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: run fixtures of Next's own e2e tests against the plugin
`pnpm conformance` runs 32 fixtures of test/e2e/app-dir of vercel/next.js
against vitest-plugin-rsc/nextjs, with Next's test files as they are. The
share that passes is a measure of how close the plugin is to Next, and
conformance/expectations.json has every test that fails with its reason: a
bug in the plugin, something the plugin does not do yet, or a test that
does not apply. A test that fails without being in it fails the run, and so
does one that is in it and passes.
The tests run in the tab the plugin runs the app in, on a shim of
nextTestSetup(), `next` and Next's Playwright wrapper. The fixtures are
fetched at the tag of the installed `next`, not vendored. A fixture is a run
of Vitest of its own. docs/next-conformance.md has the results and how it
works.
It is not a part of `pnpm test`. A workflow of its own runs it when the
runner changes, by hand, and weekly against next@canary.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: take the refresh off every redirect tag of a page
Next sends one <meta http-equiv="refresh"> for every redirect() of a render,
all with the same id. Only the first lost its http-equiv.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: have the conformance runner tell every result that differs
After a review of the runner:
- A fixture whose run is not what expectations.json says runs once more,
and the run names every test that ended otherwise. On a fresh checkout
Vite finds a dependency late and two tests of next-image fail once; on a
busy runner a test can wait just too short.
- expectations.json has the number of passing tests of every fixture, so a
test that passed and is skipped or gone fails the run. A failure with a
message that its reason does not have fails it too, and so does a fixture
that did not get to its end, which --update now leaves as it was.
- A gate of Next on a describe reaches the tests of nested describes: it is
Vitest's `fails`, which the reporter reads.
- The shim waits as long as Next's own setup, takes the body of a response
for the end of a request, and keeps its own words out of the page's log.
- The runner stops its fixtures when it is stopped, and one that cannot be
copied or read fails by itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: have @next/routing next to next for the conformance fixtures
A plugin that resolves the route of a request with `@next/routing` looks
for the package from the app, which is a copy of a fixture under
`conformance/`. Without it `--plugin` of such a checkout ended every
fixture with a startup error. The workflow installs it with `next` when
it runs another Next.js.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: run Next's server on its Node.js runtime instead of edge (#69)
Next has deprecated its edge runtime, and Cache Components, "use cache" and
proxy.ts only exist for Node.js. The server layers are compiled for Next's
Node.js runtime now, for every route, without an option of the plugin's own:
Next's flag is `export const runtime`, and its default is nodejs. A route
that asks for edge runs on Node.js too, with one warning at startup.
Pages and route handlers are served by the request handlers Next's build
makes for them, templates/app-page-runtime and templates/app-route, and the
response is written to Next's own MockedResponse. What a Node.js server has
and a tab does not is in node-platform.ts, node-server.ts and globals.ts.
The edge entries, the two edge templates and the internal export
vitest-plugin-rsc/nextjs/app-page-entrypoint are gone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: give watch mode and vitest --changed a plugin and a directory of their own
They were two modules that the plugin of Next wired in at five places, and
one of them had grown into a single function with the cache, the walk of the
module graphs, the reporter and the lookup in it.
Now src/nextjs/affected/ is the whole of it, with a plugin of its own: the
plugin of Next lists it, and rsc.ts tells it what a test file loads. Its
index.ts says how to take it out. What leans on the inside of Vitest is in
vitest.ts and nowhere else, and vitest.test.ts runs that against the real
Vitest, so an update that changes it fails a test.
Watch mode now also runs the test file that called a Server Action of a
module no page imports, like the lookup of a change already did.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: make the lookup of the test files of a route an option, off by default
vitestPluginNext({ affectedTests: true }) turns on what watch mode and
vitest --changed do for a route. It leans on how Vitest works inside, so it
is for a project to choose. Both playgrounds set it.
From the review of the move to its own directory:
- a test file is what test.include says again, not what Vitest's
matchesTestGlob says: that one counts a file with tests in its source,
reads a changed file that may be gone, and adds it to the test files;
- the test of the watch hook now shows that it runs before Vitest's lookup;
- the name of Vitest's own environment is in vitest.ts with the rest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: forget the test files of a run that was cut short
A bail or a stop skips the tests that are left of a file that runs, and that
file still passes. What it loaded was written down as all it depends on, so a
later vitest --changed could miss it. A run that Vitest ends as interrupted
now writes none of its test files down.
Also from the review: the path of a test file from the tab is normalized, and
the test of the watch hook no longer hears the disk, where a late event for a
file it wrote was a change of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: let the test of the watch hook wait for a run of its own
Vitest's watcher also finds the files of the project when it starts, and a
test file it finds runs again on its own account. The test asked for one
exact second run, which that could add a test file to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: resolve the route of a request with Next's own @next/routing (#71)
A request now goes through a server in front of the app before a route
gets it, as in a deployment: the redirects, rewrites and headers of
next.config, the redirect of a trailing slash, and proxy.ts or
middleware.ts.
None of that is routing code of the plugin. The plugin is a deployment
adapter to Next: project.ts runs the end of `next build`,
`handleBuildComplete()`, and gets the routes Next hands an adapter in
`onBuildComplete`. `resolveRoutes()` of `@next/routing` resolves a request
with them, in the tab. The request handler of the proxy is the template
Next's build expands for it, through `next-middleware-loader`. A route
gets its params in the meta of its request, read with Next's own matcher.
`@next/routing` is not a part of `next`: a project installs it at the
version of its `next`, and a run stops when the two differ.
Interception routes work with this, without code of the plugin: Next's
build makes a rewrite of one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: what Next's own tests and a review of the whole plugin found
Three tests of Next's e2e suite passed before the server ran on Next's
Node.js runtime and routes were resolved with @next/routing, and failed since:
- draftMode().enable() in a route handler was answered with a 500. The keys
of draft mode reached the tab through process.env, which a file of the
plugin does not have them in. They come with the manifest now.
- A request for /_not-found itself was answered with a 200.
- A Server Action lost an uploaded file whose name has a character like the
katakana te. The latin1 of a browser's TextDecoder is windows-1252, where a
byte like 0x83 is a character past 255, and busboy drops such a name.
From the review:
- The paths of a jsconfig.json were not resolved, and those of a tsconfig not
for a .js file it leaves out. Next resolves them for every file of the app,
and so does the plugin now, with what Next's own loader of the config reads.
- A page without a root layout ended the Vitest process, with a line of
Next's that does not name the plugin. It is an error at startup now.
- An inline script of a page ran before the URL was the page's.
- A page that could not be left failed every later test in the tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: say what the review found is different from a deployment
React's development build, HttpOnly cookies, the path of a cookie, requests
that are not a fetch, a file that is added to a route in watch mode, and an
app with cacheComponents.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: give a Server Action an uploaded file whose name is not Latin-1
Next's Node.js server reads the body of a Server Action with busboy, which
reads the headers of a part with `latin1Slice()` of Node's `Buffer`. The
stand-in decoded with `new TextDecoder("latin1")`, which is windows-1252:
other characters for the bytes 0x80 to 0x9F. A file name with such a byte
in its UTF-8, like `テ`, came out as another name, and the action got no
file. `latin1Slice()` and `asciiSlice()` are a character per byte now, as
Node has them.
Found by `should support uploading files` of Next's own `actions` fixture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: move the conformance expectations to the Node.js runtime and @next/routing
The plugin runs Next's server on its Node.js runtime and resolves the
route of a request with `@next/routing` now. Of the 484 tests, 360 pass
where 317 did: the proxy, the redirects and rewrites of `next.config`,
`trailingSlash` and interception routes are no reasons anymore.
What still fails in the five fixtures that were picked for those has the
reason it has now: an API route of the Pages Router, a prerendered page,
an `HttpOnly` cookie, and two bugs of the server in front of the app.
Two tests that passed fail: `draftMode().enable()` in a route handler,
and a request for `/_not-found` itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* revert: give a Server Action an uploaded file whose name is not Latin-1
The base branch has its own fix for the same cause, with a test of its
own: 1f47831. This reverts commit 433c3a3, so that the two do not meet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat: render a node in the layouts of the route of its url, with layouts: true
renderServer(<Node />, { url, layouts: true }) renders the node as the page
of the app's route for that URL: in its layouts from the root layout down,
with its loading, error and not-found, and with the slots of its parallel
routes. For a node that needs what the layouts give it, like providers,
global CSS or the data a layout reads, where a wrapper had to rebuild that.
The entry is the one Next's app loader writes for the route, with the module
of its page replaced by the node. The response is a document, so there is no
container to pass, and a URL that is no page of the app is an error.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: take the three tests the base fixed out of the conformance expectations
`draftMode().enable()` in a route handler, a request for `/_not-found`
itself and an uploaded file with a name that is not Latin-1 pass again
with 1f47831 of the base: 362 of the 484 tests pass. `renderServer()` no
longer rejects for the page of the demo that redirects while it loads, so
that is not in the document anymore.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: say which Not Yet causes of the conformance run the routes document lacks
The base added React's development build and an HttpOnly cookie to its
list, so the sentence that named them as missing was no longer true.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: remove dead code, doubled logic and stale claims
- the route modules are all of the rsc layer: no layer per list
- one fetch for the browser and the server, where there were two copies
- the paths of the tsconfig are resolved once, by Next's own reading of it,
and no longer by Vite's tsconfigPaths as well
- utilts.ts is utils.ts
- the probe scripts for watch mode are gone: the tests cover it
- docs: the paths row, draft mode and the layouts section say what the code does
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: a node with layouts needs a page file, and a package wins from a baseUrl
- layouts: true is an error for a route of slots only: there the node took
the place of the page of a slot
- an explicit baseUrl of the tsconfig is looked in after node_modules, as in
Next's build, and paths leave out a declaration file and a pattern with
more than one star
- the tests of a node are a file of their own, node.test.tsx
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: one module per route, and tests by what they are about
- a route handler's module no longer re-exports a second module of the same
route: every route has one module, and two lists say how to load them
- takesRequest() loses a parameter that was always true
- the check of an export of Next's runtime only says whether it is there:
the code that read its declaration and its parameters had no reader left
- vitestPluginRSC() tells Vite to find Vite RSC's Flight codec from this
package. A project on pnpm no longer needs @vitejs/plugin-rsc of its own,
and the playgrounds no longer list it
- the tests of a node and of what the tab lets go of are files of their own
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: loadNextProject() is a list of steps, each in a file of its own
project.ts was one function of 1400 lines. It keeps the types and the steps;
the code of each step is in project/, moved as it was:
- context.ts the project, its next.config, and every file and export of
Next's build that the plugin calls
- routes.ts the routes of the app, as next build lists them
- routing.ts the middleware, and the routes Next's build hands an adapter
- runtime.ts what the tab assumes of Next's runtime
- layers.ts the constants and aliases of each layer, the Flight exports
- entries.ts the entry of a route, of a node, and of the middleware
- transform.ts Next's SWC transform
- loaders.ts Next's loaders for fonts and images, and its image optimizer
The require of the project is projectRequire there: a bare require type-checks
as Node's global, which a module does not have.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: rewrite the design document in plain technical English
The same sections, claims, code blocks and links, in sentences that read
once: possessives and compound nouns for stacked genitives, one idea per
sentence. Also says that the build code of Next is called from project.ts
and project/, that a change of test files only is the one exception to
"never too few", and what the onMatch routes are.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: lint what the type checker let through, and shorten handle()
- a bare require, module or exports in the source of the package is a lint
error: they type-check as Node's globals, which an ES module does not have
- unused variables and imports are a lint error again
- the route of a node and the Server Action of a request are functions of
their own in ssr.ts
After the split of project.ts, generateBuildId() of next.config and the
defines of each layer are computed earlier at startup than before. Their
values are the same; an app with more than one startup error can see another
one first.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: set the 404 of the not-found page before Next's handler runs
A request that no route has gets the not-found page. Its status was put on
the response after Next's handler had run, where `next start` sets it before.
So Next rendered the page as a 200, without
`<meta name="robots" content="noindex">`, and a Server Action request for
such a path was answered 404 where Next's action handler answers 400 or 409.
The status is on Next's response before the handler starts now, and the
response has the status the handler leaves.
Next answers 400 for an id that cannot be a server reference id, and 409 for
one that it does not have. The stand-in for `mightBeServerReferenceId()` took
any string for one, so 400 never happened. An id can be one when it has Next's
shape or Vite RSC's, `<module>#<export>`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: take Next's internal headers off a request that comes in
`next start` drops the headers that only its own layers set on a request,
before anything reads the request. The plugin did not, so a request that
brought `x-middleware-set-cookie` along had a cookie the app never set.
The server in the tab calls Next's own `filterInternalHeaders()` on a request
where it comes in, before the route resolution and the proxy. The proxy still
puts its cookies on the request for the route, after that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix: pre-bundle what a cold run found while a test ran
With a cold Vite cache, the first run of an app could fail where the second
passed: Vite found a dependency while a test ran, pre-bundled again and
reloaded the tab. Two causes:
- The dependency scan had the files of `app/` for its entries, and not the
proxy. What only `proxy.ts` imports, like `next/cache`, was found when the
first request ran it.
- A Flight payload names a Client Component of Next by its file, like
`next/dist/esm/client/legacy/image.js`. The plugin pre-bundled the ones it
reached from Next's alias table and its own imports, and the app imports
this one as `next/legacy/image`. It pre-bundles every file of Next's ESM
build with a `"use client"` directive now, which is the condition the rsc
layer makes a client reference by.
A test on the config the plugin makes for the demo has both, and that every
module of Next that a module of the plugin imports in the tab is listed for
pre-bundling.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: record the eight conformance tests the three fixes make pass
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs: lead the README with the examples it had, and sell instead of listing gaps
The component examples of main fit the API again: a node on its own, its
params from the app's route for the URL, a navigation that opens the page.
The list of what does not work yet is gone from the README; docs/next-routes.md
keeps it for whoever works on the plugin.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor: scan Next's client boundaries once per installation, and tidy the new tests
The scan reads every file of Next's ESM build, and ran for every project. The
test of the plugin's config loads the demo once, the tests restore their
console spies when they finish, and the test of the internal headers checks
that the page rendered.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: record the conformance test that #77 makes fail
A form without a Server Action that the browser posts to / now ends at
the URL with the query of the test's tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11519
A
vi.mockin a setup file doesn't work in browser mode when the test file imports the module with a normalimport:This is a regression in Vitest 5. It worked in 4.1.11 and 5.0.0-beta.6, and fails in 5.0.0 and 5.0.3.
vi.mockonly queues the mock. Setting it up takes a round trip to the server, and nothing waits for that before the test file is imported.Now the browser runner waits for the mocks before it imports a file. In Node, Vitest already waits for the mocks before it loads a module.
For the test I had to slow down the mock in the fixture by 500ms. Without that delay the mock happens to be ready in time in Vitest's own test suite, and the test passes even without the fix.
The text is in my own words. Opus 5.5 found the fix and made the reproduction, reviewed by Fable 5.1.