Files
vercel__next.js/test/e2e/filesystem-cache/evict-after-snapshot.test.ts
Tobias Koppers 357b2b3c4c Use pnpm for isolated test installs (#98425)
### What?

Migrate the five local isolated test installs that explicitly used npm
to pnpm. The Nx fixture now has pnpm workspace metadata, while
filesystem-layout-sensitive fixtures use pnpm's hoisted linker with
copied package files.

### Why?

The npm-based Nx install bypassed the repository's centralized
supply-chain protections and could select a package immediately after
publication, including temporarily incomplete multi-package releases.
Using pnpm makes isolated installs inherit the repository's
`minimumReleaseAge`, exclusions, and exotic-subdependency policy.

The other npm installs depended on npm-style real package directories.
On Node versions affected by nodejs/node#65113, hoisted/copy mode
preserves that layout without leaving these fixtures outside the shared
pnpm security configuration; fixed Node releases use normal pnpm
linking.

### How?

- Use normal pnpm workspace resolution for the Nx fixture.
- Use `node-linker=hoisted` and `package-import-method=copy` for
filesystem tests only on affected Node releases; Node 24.21+ and 26.8+
use normal linking. Node 20 CI keeps the workaround because no fixed
Node 20 release exists.
- Validate local `@next/env` tarballs through the lockfile when hoisted
installs do not expose pnpm's virtual-store path marker.
- Keep the deployment-environment npm install unchanged.

### Verification

- `pnpm build-all`
- `pnpm types`
- A 9-version throwaway assertion verified the affected/fixed Node
release matrix
- `pnpm test-dev-turbo test/e2e/app-dir/nx-handling/nx-handling.test.ts
test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
test/e2e/filesystem-cache/filesystem-cache.test.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts
test/e2e/filesystem-cache/evict-after-snapshot.test.ts` — all 25 tests
passed after installing the sandbox's missing Playwright browser
- Production Turbopack: Nx, non-hoisted SWC helper, build-cache-default,
and warm restart passed (9/9)
- `filesystem-cache.test.ts` production baseline: 15/17 passed; the same
two cache-growth bounds fail under both the unchanged npm fixture and
the pnpm fixture at nearly identical percentages, so they are
pre-existing sandbox-specific failures
- Generated-layout inspection: no package symlinks outside expected
`.bin` command shims; package files are copied; `node_modules/.pnpm` is
metadata-only

<!-- NEXT_JS_LLM -->


<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 20:19:48 +00:00

90 lines
2.7 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
import { getPnpmRealpathWorkaround } from '../../lib/pnpm-realpath-workaround'
import { retry, waitFor } from 'next-test-utils'
// Eviction requires the dev server (HMR) and persistent caching (Turbopack).
// Skip entirely in prod/start mode.
// TODO(deploy-test-completion): Re-enable this suite in deploy mode.
// It likely mutates files in the isolated local fixture after setup.
// @force-gate !deploy
// @force-gate dev
describe('evict-after-snapshot', () => {
const env = {
ENABLE_CACHING: '1',
TURBO_ENGINE_IGNORE_DIRTY: '1',
TURBO_ENGINE_SNAPSHOT_IDLE_TIMEOUT_MILLIS: '1000',
// Persist even tiny snapshots so the test doesn't depend on the
// minimum-compilation-time threshold.
TURBO_ENGINE_SNAPSHOT_MIN_ACTIVE_TIME_MILLIS: '0',
ENABLE_EVICTION: '1',
}
const { next } = nextTestSetup({
files: __dirname,
overrideFiles: getPnpmRealpathWorkaround(),
env,
})
async function waitForSnapshotAndEviction() {
// The idle timeout is 1s, give extra time for snapshot + eviction to complete
await waitFor(5000)
}
// Turbopack-only: eviction requires persistent caching
;(process.env.IS_TURBOPACK_TEST ? it : it.skip)(
'should serve correct content after eviction and HMR',
async () => {
const browser = await next.browser('/')
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('hello world')
})
let currentContent = 'hello world'
for (let cycle = 1; cycle <= 3; cycle++) {
await waitForSnapshotAndEviction()
const prevContent = currentContent
const nextContent = `cycle ${cycle}`
await next.patchFile('app/page.tsx', (content) =>
content.replace(prevContent, nextContent)
)
currentContent = nextContent
const expected = currentContent
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe(expected)
}, 10000)
}
await browser.close()
},
90000
)
;(process.env.IS_TURBOPACK_TEST ? it : it.skip)(
'should handle client component HMR after eviction',
async () => {
const browser = await next.browser('/client')
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('hello world')
})
await waitForSnapshotAndEviction()
await next.patchFile(
'app/client/page.tsx',
(content) => content.replace('hello world', 'hello eviction'),
async () => {
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe(
'hello eviction'
)
}, 10000)
}
)
await browser.close()
},
90000
)
})