Add tests for use cache over-invalidation and under-invalidation in dev
In dev, "use cache" entries must be discarded exactly when something they
can depend on may have changed. Pre-existing behaviors violate this in both
directions, and this commit adds failing tests for them.
Over-invalidation: adding or removing any page file — and every dev server
start — currently discards all cached entries on webpack. The dev-only HMR
refresh hash is part of every "use cache" key, and the hot reloader
refreshes it on any rebuild that follows
`invalidate({ reloadAfterInvalidation: true })`. setup-dev-bundler passes
`reloadAfterInvalidation: envChange`, but `envChange` is also set whenever
the client router filter changes: on every start (the first watcher pass
computes the filter for the first time) and whenever a page file is added or
removed. Neither affects cached data. The start-time refresh lands right
after the first compile finishes; when a page request beats it, the entries
that request just cached are discarded and the next reload is silently cold.
That race is the cause of the cache-components-dev-warmup flakes on loaded
machines (Prerender labels flipping to Server on warm reloads). The race
can't be reproduced deterministically, so the coverage goes through the page
add trigger, which takes the same code path: this commit tightens the page
add/remove notification suite's re-fetch count, so an open tab must re-fetch
exactly once when a page is added, on both bundlers, where the test
previously expected the announcement re-fetch plus the false alarm's.
Turbopack has the same bug on different timing: it advances its hash once
per change-subscription emission, and an added or removed page's
subscription can emit without an edit. A later PR in the stack derives the
hash from the compiled output, closing that hole by construction, and tests
the cached entries directly there.
Turbopack does react to the false alarm in another way, though: it clears
the server's module cache, so a page add resets module state. A test pins
this with a module-scope render counter that must keep counting across an
unrelated page add. It's Turbopack-only: on webpack, adding a page
recompiles the server bundle (the compiled-in client router filter changes),
which replaces the serving module instances regardless.
Under-invalidation: entries persisted by a custom cache handler are currently
served across dev server runs, even though the code may have changed while
the server was down. Before the first edit the hash is unset (webpack) or a
constant ("0" in Turbopack), so keys collide across runs; after an edit,
webpack keys carry a content-derived `stats.hash`, so a run that re-applies
an edit a previous run also made collides with that run's persisted entries.
One test covers the pre-edit collision via a restart (guarding against a
vacuous pass by checking that entries were actually persisted), and one
covers the post-edit collision by re-applying a previous run's edit.
Two more tests pin down behavior that must survive a fix: an env file change
that the page's output depends on must still invalidate cached entries, and
an env file change that doesn't affect compiled output must still make the
browser refetch server components without a manual reload.
Without the fixes, the re-applied edit test and the re-fetch count test fail
on webpack on an idle machine, and the re-fetch count and module state tests
fail on Turbopack the same way. The restart test fails on both, except when
the two webpack runs happen to cache on opposite sides of their start-time
refresh — the refresh swaps the unset hash for a content-derived one, so
entries keyed on opposite sides don't collide, and the cross-run reuse this
test is about doesn't occur that run.