import { isNextDev, nextTestSetup } from 'e2e-utils'
describe('node-worker-threads', () => {
const { next, skipped, isTurbopack } = nextTestSetup({
files: __dirname,
skipDeployment: true,
dependencies: {
pino: '9.6.0',
jspdf: '4.2.1',
},
})
if (skipped) {
return
}
// These tests are Turbopack-specific since they rely on Turbopack's worker bundling
if (!isTurbopack) {
it.skip('webpack doesnt support bundling worker-threads', () => {})
return
}
it('should handle simple worker with relative path', async () => {
const res = await next.fetch('/api/simple-worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('pong from simple worker')
})
it('should handle self-referencing worker with __filename', async () => {
const res = await next.fetch('/api/worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('pong')
})
it('should handle pino logger with transport (thread-stream)', async () => {
// Pino with transports uses thread-stream internally, which creates worker_threads
// with a broad pattern like join(__dirname, 'lib', 'worker.js') that can match
// non-evaluatable files like package.json. This tests that we properly downgrade
// those errors to warnings via loose_errors.
const res = await next.fetch('/api/pino-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
})
it('should not expose __turbopack internal workerData to user code', async () => {
// Verify that internal __turbopack_globals__ data used to forward globals
// is not visible to user code accessing workerData
const res = await next.fetch('/api/workerdata-check')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
// The __turbopack_globals__ key should NOT be visible to user code
expect(data.hasTurbopackKeys).toBe(false)
expect(data.turbopackKeys).toEqual([])
})
it('should handle jsPDF which uses Worker with eval: true (issue #91642)', async () => {
// jsPDF internally creates Worker threads with { eval: true }, passing
// inline JS code instead of a file path. Turbopack should not try to
// resolve the first argument as a module reference in this case.
const res = await next.fetch('/api/jspdf-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.size).toBeGreaterThan(0)
})
it('should handle PNG file import in worker', async () => {
// Test that static assets (like PNG images) can be imported and used in workers
// The server worker returns the PNG URL, then we fetch it from the client
// to verify the URL is correctly formed and accessible
const res = await next.fetch('/api/png-worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.pngInfo).toBeDefined()
expect(data.pngInfo.width).toBe(1)
expect(data.pngInfo.height).toBe(1)
const url = new URL(data.pngInfo.url, 'http://localhost')
expect(url.pathname).toMatch(
/\/_next\/static.*\/test-image\.[0-9a-z_-]+\.png/
)
if (!isNextDev) {
if (next.assetToken) {
expect(url.searchParams.get('dpl')).toBe(next.assetToken)
} else {
expect(url.searchParams.get('dpl')).toBeNull()
}
}
// Now fetch the PNG URL from the client to verify it's accessible
// This tests that the URL generated by the server worker is correct
const pngRes = await next.fetch(data.pngInfo.url)
expect(pngRes.status).toBe(200)
expect(pngRes.headers.get('content-type')).toBe('image/png')
})
})