mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
*This doesn't enable skew protection for any new tests, only preparation* Prepare a lot of test assertions so that they work correctly when a `?dpl=123` query param is present: - some regexes should accept the query - some places assumed that the `<script>`'s `src` is a valid pathname, whereas it might have a query param that has to be stripped before removing the path - some hardcoded build ids instead of `next.buildId`
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
import { join } from 'path'
|
|
import { nextTestSetup } from 'e2e-utils'
|
|
import { getClientReferenceManifest, retry } from 'next-test-utils'
|
|
import { parseRelativeUrl } from 'next/dist/shared/lib/router/utils/parse-relative-url'
|
|
|
|
function getServerReferenceIdsFromBundle(source: string): string[] {
|
|
// Reference IDs are strings with [0-9a-f] that are at least 32 characters long.
|
|
// We use RegExp to find them in the bundle.
|
|
const referenceIds = source.matchAll(/"([0-9a-f]{32,})"/g) || []
|
|
return [...referenceIds].map(([, id]) => id)
|
|
}
|
|
|
|
describe('app-dir - client-actions-tree-shaking', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
const logs: string[] = []
|
|
|
|
beforeAll(() => {
|
|
const onLog = (log: string) => {
|
|
logs.push(log.trim())
|
|
}
|
|
next.on('stdout', onLog)
|
|
next.on('stderr', onLog)
|
|
})
|
|
|
|
afterEach(async () => {
|
|
logs.length = 0
|
|
})
|
|
|
|
/**
|
|
* Parses the client reference manifest for a given route and returns the client chunks
|
|
*/
|
|
function getClientChunks(route: string): Array<string> {
|
|
const clientManifest = getClientReferenceManifest(next, route)
|
|
const chunks = new Set<string>()
|
|
if (process.env.IS_TURBOPACK_TEST) {
|
|
// These only exist for turbopack and are encoded as files
|
|
// entryJSFiles is a map of module name to a set of chunks relative to `.next`
|
|
for (const entries of Object.values(clientManifest.entryJSFiles)) {
|
|
for (const chunk of entries) {
|
|
chunks.add(chunk)
|
|
}
|
|
}
|
|
// clientModules is a mapping from module name to a set of URLs
|
|
// So strip that prefix and add it to the chunks
|
|
for (const clientModule of Object.values(clientManifest.clientModules)) {
|
|
for (const chunk of clientModule.chunks) {
|
|
chunks.add(parseRelativeUrl(chunk).pathname.replace('/_next/', ''))
|
|
}
|
|
}
|
|
} else {
|
|
// webpack doens't use entryJSFiles, so we need to use clientModules but the format is different.
|
|
// chunks is a sequence of 'chunk-id', chunk-path pairs, so we need to skip the chunk-id
|
|
for (const clientModule of Object.values(clientManifest.clientModules)) {
|
|
for (let i = 1; i < clientModule.chunks.length; i += 2) {
|
|
chunks.add(clientModule.chunks[i])
|
|
}
|
|
}
|
|
}
|
|
return Array.from(chunks)
|
|
}
|
|
|
|
it('should not bundle unused server reference id in client bundles', async () => {
|
|
const bundle1Files = getClientChunks('/route-1/page')
|
|
const bundle2Files = getClientChunks('/route-2/page')
|
|
const bundle3Files = getClientChunks('/route-3/page')
|
|
|
|
const bundle1Contents = await Promise.all(
|
|
bundle1Files.map((file: string) =>
|
|
next.readFile(join(next.distDir, file))
|
|
)
|
|
)
|
|
const bundle2Contents = await Promise.all(
|
|
bundle2Files.map((file: string) =>
|
|
next.readFile(join(next.distDir, file))
|
|
)
|
|
)
|
|
const bundle3Contents = await Promise.all(
|
|
bundle3Files.map((file: string) =>
|
|
next.readFile(join(next.distDir, file))
|
|
)
|
|
)
|
|
|
|
const bundle1Ids = bundle1Contents.flatMap((file: string) =>
|
|
getServerReferenceIdsFromBundle(file)
|
|
)
|
|
const bundle2Ids = bundle2Contents.flatMap((file: string) =>
|
|
getServerReferenceIdsFromBundle(file)
|
|
)
|
|
const bundle3Ids = bundle3Contents.flatMap((file: string) =>
|
|
getServerReferenceIdsFromBundle(file)
|
|
)
|
|
|
|
// Bundle 1 and 2 should only have one ID.
|
|
expect(bundle1Ids).toHaveLength(1)
|
|
expect(bundle2Ids).toHaveLength(1)
|
|
expect(bundle1Ids[0]).not.toEqual(bundle2Ids[0])
|
|
|
|
// Bundle 3 should have no IDs.
|
|
expect(bundle3Ids).toHaveLength(0)
|
|
})
|
|
|
|
// Test the application
|
|
it('should trigger actions correctly', async () => {
|
|
const browser = await next.browser('/route-1')
|
|
await browser.elementById('submit').click()
|
|
|
|
await retry(() => {
|
|
expect(logs).toEqual(
|
|
expect.arrayContaining([expect.stringContaining('This is action foo')])
|
|
)
|
|
})
|
|
|
|
const browser2 = await next.browser('/route-2')
|
|
await browser2.elementById('submit').click()
|
|
|
|
await retry(() => {
|
|
expect(logs).toEqual(
|
|
expect.arrayContaining([expect.stringContaining('This is action bar')])
|
|
)
|
|
})
|
|
})
|
|
})
|