Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
921 lines
40 KiB
TypeScript
921 lines
40 KiB
TypeScript
import * as path from 'path'
|
||
import { nextTestSetup } from 'e2e-utils'
|
||
import stripAnsi from 'strip-ansi'
|
||
import { retry } from 'next-test-utils'
|
||
|
||
function normalizeCliOutput(output: string) {
|
||
return (
|
||
stripAnsi(output)
|
||
// TODO(veil): Should not appear in sourcemapped stackframes.
|
||
.replaceAll('webpack:///', 'bundler:///')
|
||
.replaceAll(/at [a-zA-Z] \(/g, 'at <mangled> (')
|
||
)
|
||
}
|
||
|
||
describe('app-dir - server source maps', () => {
|
||
const dependencies = {
|
||
// `link:` simulates a package in a monorepo
|
||
'internal-pkg': `link:./internal-pkg`,
|
||
'external-pkg': `file:./external-pkg`,
|
||
}
|
||
const { skipped, next, isNextDev, isTurbopack, isRspack } = nextTestSetup({
|
||
dependencies,
|
||
files: path.join(__dirname, 'fixtures/default'),
|
||
// Deploy tests don't have access to runtime logs.
|
||
// Manually verify that the runtime logs match.
|
||
skipDeployment: true,
|
||
})
|
||
|
||
if (skipped) return
|
||
|
||
it('logged errors have a sourcemapped stack with a codeframe', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'Error: rsc-error-log'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'Error: rsc-error-log' +
|
||
'\n at logError (app/rsc-error-log/page.js:4:17)' +
|
||
'\n at Page (app/rsc-error-log/page.js:9:3)' +
|
||
'\n 2 |' +
|
||
'\n 3 | function logError() {' +
|
||
"\n> 4 | const error = new Error('rsc-error-log')" +
|
||
'\n | ^' +
|
||
'\n 5 | console.error(error)' +
|
||
'\n 6 | }' +
|
||
'\n 7 |' +
|
||
'\n'
|
||
)
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths in webpack
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/rsc-error-log/page.js:4:17)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
"\n> 4 | const error = new Error('rsc-error-log')" +
|
||
'\n | ^'
|
||
)
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('logged errors have a sourcemapped `cause`', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log-cause')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'Error: rsc-error-log-cause'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'Error: rsc-error-log-cause' +
|
||
'\n at logError (app/rsc-error-log-cause/page.js:2:17)' +
|
||
'\n at Page (app/rsc-error-log-cause/page.js:8:3)' +
|
||
'\n 1 | function logError(cause) {' +
|
||
"\n> 2 | const error = new Error('rsc-error-log-cause', { cause })" +
|
||
'\n | ^' +
|
||
'\n 3 | console.error(error)' +
|
||
'\n 4 | }' +
|
||
'\n 5 | {' +
|
||
'\n [cause]: Error: Boom' +
|
||
'\n at Page (app/rsc-error-log-cause/page.js:7:17)' +
|
||
'\n 5 |' +
|
||
'\n 6 | export default function Page() {' +
|
||
"\n > 7 | const error = new Error('Boom')" +
|
||
'\n | ^' +
|
||
'\n 8 | logError(error)' +
|
||
'\n 9 | return null' +
|
||
'\n 10 | }' +
|
||
'\n'
|
||
)
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/rsc-error-log-cause/page.js:2:17)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/rsc-error-log-cause/page.js:7:17)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
"\n> 2 | const error = new Error('rsc-error-log-cause', { cause })" +
|
||
'\n | ^'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
"\n > 7 | const error = new Error('Boom')" +
|
||
'\n | ^'
|
||
)
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('logged errors collapse deeply nested causes at depth 2', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log-nested')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'Error: rsc-error-log-nested'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'Error: rsc-error-log-nested' +
|
||
'\n at logError (app/rsc-error-log-nested/page.js:6:18)' +
|
||
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
|
||
"\n 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
|
||
"\n 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
|
||
"\n> 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
|
||
'\n | ^' +
|
||
'\n 7 | console.error(depth0)' +
|
||
'\n 8 | }' +
|
||
'\n 9 | {' +
|
||
'\n [cause]: Error: Depth 1 error' +
|
||
'\n at logError (app/rsc-error-log-nested/page.js:5:18)' +
|
||
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
|
||
"\n 3 | const depth3 = new Error('Depth 3 error', { cause: depth4 })" +
|
||
"\n 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
|
||
"\n > 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
|
||
'\n | ^' +
|
||
"\n 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
|
||
'\n 7 | console.error(depth0)' +
|
||
'\n 8 | } {' +
|
||
'\n [cause]: Error: Depth 2 error' +
|
||
'\n at logError (app/rsc-error-log-nested/page.js:4:18)' +
|
||
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
|
||
"\n 2 | const depth4 = new Error('Depth 4 error')" +
|
||
"\n 3 | const depth3 = new Error('Depth 3 error', { cause: depth4 })" +
|
||
"\n > 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
|
||
'\n | ^' +
|
||
"\n 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
|
||
"\n 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
|
||
'\n 7 | console.error(depth0) {' +
|
||
'\n [cause]: [Error]' +
|
||
'\n }' +
|
||
'\n }' +
|
||
'\n}'
|
||
)
|
||
// Verify depth 3+ are NOT shown (truncated to [Error])
|
||
expect(
|
||
normalizeCliOutput(next.cliOutput.slice(outputIndex))
|
||
).not.toContain('Error: Depth 3 error')
|
||
expect(
|
||
normalizeCliOutput(next.cliOutput.slice(outputIndex))
|
||
).not.toContain('Error: Depth 4 error')
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths in production
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/rsc-error-log-nested/page.js:6:18)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain('[cause]: [Error]')
|
||
expect(normalizeCliOutput(next.cliOutput)).not.toContain(
|
||
'Error: Depth 3 error'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).not.toContain(
|
||
'Error: Depth 4 error'
|
||
)
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('logged errors include `[errors]` for AggregateError', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log-aggregate')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'AggregateError: rsc-error-log-aggregate'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'AggregateError: rsc-error-log-aggregate' +
|
||
'\n at logError (app/rsc-error-log-aggregate/page.js:6:26)' +
|
||
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
|
||
"\n 4 | const error2 = new TypeError('Error 2')" +
|
||
"\n 5 | const rootError = new Error('Root error')" +
|
||
'\n> 6 | const aggregateError = new AggregateError(' +
|
||
'\n | ^' +
|
||
'\n 7 | [error1, error2],' +
|
||
"\n 8 | 'rsc-error-log-aggregate'," +
|
||
'\n 9 | { cause: rootError } {' +
|
||
'\n [cause]: Error: Root error' +
|
||
'\n at logError (app/rsc-error-log-aggregate/page.js:5:21)' +
|
||
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
|
||
"\n 3 | const error1 = new Error('Error 1')" +
|
||
"\n 4 | const error2 = new TypeError('Error 2')" +
|
||
"\n > 5 | const rootError = new Error('Root error')" +
|
||
'\n | ^' +
|
||
'\n 6 | const aggregateError = new AggregateError(' +
|
||
'\n 7 | [error1, error2],' +
|
||
"\n 8 | 'rsc-error-log-aggregate',," +
|
||
'\n [errors]: [' +
|
||
'\n Error: Error 1' +
|
||
'\n at logError (app/rsc-error-log-aggregate/page.js:3:18)' +
|
||
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
|
||
'\n 1 | /* global AggregateError */' +
|
||
'\n 2 | function logError() {' +
|
||
"\n > 3 | const error1 = new Error('Error 1')" +
|
||
'\n | ^' +
|
||
"\n 4 | const error2 = new TypeError('Error 2')" +
|
||
"\n 5 | const rootError = new Error('Root error')" +
|
||
'\n 6 | const aggregateError = new AggregateError(,' +
|
||
'\n TypeError: Error 2' +
|
||
'\n at logError (app/rsc-error-log-aggregate/page.js:4:18)' +
|
||
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
|
||
'\n 2 | function logError() {' +
|
||
"\n 3 | const error1 = new Error('Error 1')" +
|
||
"\n > 4 | const error2 = new TypeError('Error 2')" +
|
||
'\n | ^' +
|
||
"\n 5 | const rootError = new Error('Root error')" +
|
||
'\n 6 | const aggregateError = new AggregateError(' +
|
||
'\n 7 | [error1, error2],' +
|
||
'\n ]' +
|
||
'\n}'
|
||
)
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths in production
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/rsc-error-log-aggregate/page.js:6:26)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain('[errors]:')
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain('[cause]:')
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('stack frames are ignore-listed in ssr', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
const browser = await next.browser('/ssr-error-log-ignore-listed')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'Error: ssr-error-log-ignore-listed'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
isTurbopack
|
||
? 'Error: ssr-error-log-ignore-listed' +
|
||
'\n at logError (app/ssr-error-log-ignore-listed/page.js:9:17)' +
|
||
'\n at runWithInternalIgnored (app/ssr-error-log-ignore-listed/page.js:19:13)' +
|
||
'\n at runWithExternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:18:29)' +
|
||
'\n at runWithExternal (app/ssr-error-log-ignore-listed/page.js:17:32)' +
|
||
'\n at runWithInternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:16:18)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternalSourceMapped (internal-pkg/sourcemapped.ts:5:10)' +
|
||
'\n at runWithInternal (app/ssr-error-log-ignore-listed/page.js:15:28)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternal (internal-pkg/index.js:2:10)' +
|
||
'\n at Page (app/ssr-error-log-ignore-listed/page.js:14:14)' +
|
||
'\n 7 |' +
|
||
'\n'
|
||
: 'Error: ssr-error-log-ignore-listed' +
|
||
'\n at logError (app/ssr-error-log-ignore-listed/page.js:9:17)' +
|
||
'\n at runWithInternalIgnored (app/ssr-error-log-ignore-listed/page.js:19:13)' +
|
||
// TODO(veil-NDX-910): Webpacks's sourcemap loader drops `ignoreList`
|
||
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
|
||
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
|
||
'\n at runInternalIgnored (webpack-internal:/(ssr)/internal-pkg/ignored.ts:6:10)' +
|
||
'\n at runWithExternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:18:29)' +
|
||
'\n at runWithExternal (app/ssr-error-log-ignore-listed/page.js:17:32)' +
|
||
'\n at runWithInternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:16:18)' +
|
||
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
|
||
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternalSourceMapped (webpack-internal:/(ssr)/internal-pkg/sourcemapped.ts:5:10)' +
|
||
'\n at runWithInternal (app/ssr-error-log-ignore-listed/page.js:15:28)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternal (internal-pkg/index.js:2:10)' +
|
||
'\n at Page (app/ssr-error-log-ignore-listed/page.js:14:14)' +
|
||
'\n 7 |' +
|
||
'\n'
|
||
)
|
||
if (isTurbopack) {
|
||
// TODO(veil): Turbopack errors because it thinks the sources are not part of the project.
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"description": "ssr-error-log-ignore-listed",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-error-log-ignore-listed/page.js (9:17) @ logError
|
||
> 9 | const error = new Error('ssr-error-log-ignore-listed')
|
||
| ^",
|
||
"stack": [
|
||
"logError app/ssr-error-log-ignore-listed/page.js (9:17)",
|
||
"runWithInternalIgnored app/ssr-error-log-ignore-listed/page.js (19:13)",
|
||
"runWithExternalSourceMapped app/ssr-error-log-ignore-listed/page.js (18:29)",
|
||
"runWithExternal app/ssr-error-log-ignore-listed/page.js (17:32)",
|
||
"runWithInternalSourceMapped app/ssr-error-log-ignore-listed/page.js (16:18)",
|
||
"runInternalSourceMapped internal-pkg/sourcemapped.ts (5:10)",
|
||
"runWithInternal app/ssr-error-log-ignore-listed/page.js (15:28)",
|
||
"runInternal internal-pkg/index.js (2:10)",
|
||
"Page app/ssr-error-log-ignore-listed/page.js (14:14)",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
// TODO(veil-NDX-910): Webpacks's sourcemap loader drops `ignoreList`
|
||
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"description": "ssr-error-log-ignore-listed",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-error-log-ignore-listed/page.js (9:17) @ logError
|
||
> 9 | const error = new Error('ssr-error-log-ignore-listed')
|
||
| ^",
|
||
"stack": [
|
||
"logError app/ssr-error-log-ignore-listed/page.js (9:17)",
|
||
"runWithInternalIgnored app/ssr-error-log-ignore-listed/page.js (19:13)",
|
||
"runInternalIgnored ignored.ts (6:10)",
|
||
"runWithExternalSourceMapped app/ssr-error-log-ignore-listed/page.js (18:29)",
|
||
"runWithExternal app/ssr-error-log-ignore-listed/page.js (17:32)",
|
||
"runWithInternalSourceMapped app/ssr-error-log-ignore-listed/page.js (16:18)",
|
||
"runInternalSourceMapped sourcemapped.ts (5:10)",
|
||
"runWithInternal app/ssr-error-log-ignore-listed/page.js (15:28)",
|
||
"runInternal internal-pkg/index.js (2:10)",
|
||
"Page app/ssr-error-log-ignore-listed/page.js (14:14)",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'(app/ssr-error-log-ignore-listed/page.js:9:17)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'\n' +
|
||
"> 9 | const error = new Error('ssr-error-log-ignore-listed')\n" +
|
||
' | ^\n'
|
||
)
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('stack frames are ignore-listed in rsc', async () => {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log-ignore-listed')
|
||
|
||
if (isNextDev) {
|
||
await retry(() => {
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'Error: rsc-error-log-ignore-listed'
|
||
)
|
||
})
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
isTurbopack
|
||
? 'Error: rsc-error-log-ignore-listed' +
|
||
'\n at logError (app/rsc-error-log-ignore-listed/page.js:8:17)' +
|
||
'\n at runWithInternalIgnored (app/rsc-error-log-ignore-listed/page.js:18:13)' +
|
||
'\n at runWithExternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:17:29)' +
|
||
'\n at runWithExternal (app/rsc-error-log-ignore-listed/page.js:16:32)' +
|
||
'\n at runWithInternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:15:18)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternalSourceMapped (internal-pkg/sourcemapped.ts:5:10)' +
|
||
'\n at runWithInternal (app/rsc-error-log-ignore-listed/page.js:14:28)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternal (internal-pkg/index.js:2:10)' +
|
||
'\n at Page (app/rsc-error-log-ignore-listed/page.js:13:14)' +
|
||
'\n 6 |' +
|
||
'\n'
|
||
: 'Error: rsc-error-log-ignore-listed' +
|
||
'\n at logError (app/rsc-error-log-ignore-listed/page.js:8:17)' +
|
||
'\n at runWithInternalIgnored (app/rsc-error-log-ignore-listed/page.js:18:13)' +
|
||
// TODO(veil): Webpacks's sourcemap loader drops `ignoreList`
|
||
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
|
||
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
|
||
'\n at runInternalIgnored (webpack-internal:/(rsc)/internal-pkg/ignored.ts:6:10)' +
|
||
'\n at runWithExternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:17:29)' +
|
||
'\n at runWithExternal (app/rsc-error-log-ignore-listed/page.js:16:32)' +
|
||
'\n at runWithInternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:15:18)' +
|
||
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
|
||
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternalSourceMapped (webpack-internal:/(rsc)/internal-pkg/sourcemapped.ts:5:10)' +
|
||
'\n at runWithInternal (app/rsc-error-log-ignore-listed/page.js:14:28)' +
|
||
// Realpath does not point into node_modules so we don't ignore it.
|
||
'\n at runInternal (internal-pkg/index.js:2:10)' +
|
||
'\n at Page (app/rsc-error-log-ignore-listed/page.js:13:14)' +
|
||
'\n 6 |' +
|
||
'\n'
|
||
)
|
||
} else {
|
||
if (isTurbopack) {
|
||
// TODO(veil): Sourcemap names
|
||
// TODO(veil): relative paths
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'at <unknown> (app/rsc-error-log-ignore-listed/page.js:8:17)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
"\n> 8 | const error = new Error('rsc-error-log-ignore-listed')" +
|
||
'\n | ^'
|
||
)
|
||
} else {
|
||
// TODO(veil): line/column numbers are flaky in Webpack
|
||
}
|
||
}
|
||
})
|
||
|
||
it('thrown SSR errors', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
const browser = await next.browser('/ssr-throw')
|
||
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain('Error: ssr-throw')
|
||
})
|
||
|
||
const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
|
||
expect(cliOutput).toContain(
|
||
'⨯ Error: ssr-throw' +
|
||
'\n at throwError (app/ssr-throw/Thrower.js:4:9)' +
|
||
'\n at Thrower (app/ssr-throw/Thrower.js:8:3)' +
|
||
'\n 2 |' +
|
||
'\n 3 | function throwError() {' +
|
||
"\n> 4 | throw new Error('ssr-throw')" +
|
||
'\n | ^' +
|
||
'\n 5 | }' +
|
||
'\n 6 |' +
|
||
'\n 7 | export function Thrower() { {' +
|
||
"\n digest: '"
|
||
)
|
||
expect(cliOutput).toMatch(/digest: '\d+'/)
|
||
|
||
await expect(browser).toDisplayRedbox(`
|
||
{
|
||
"description": "ssr-throw",
|
||
"environmentLabel": null,
|
||
"label": "Runtime Error",
|
||
"source": "app/ssr-throw/Thrower.js (4:9) @ throwError
|
||
> 4 | throw new Error('ssr-throw')
|
||
| ^",
|
||
"stack": [
|
||
"throwError app/ssr-throw/Thrower.js (4:9)",
|
||
"Thrower app/ssr-throw/Thrower.js (8:3)",
|
||
"Page app/ssr-throw/page.js (6:10)",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
// SSR errors are not logged because React retries them during hydration.
|
||
}
|
||
})
|
||
|
||
it('logged errors preserve their name', async () => {
|
||
let cliOutput = next.cliOutput
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/rsc-error-log-custom-name')
|
||
cliOutput = next.cliOutput.slice(outputIndex)
|
||
}
|
||
|
||
await retry(() => {
|
||
expect(cliOutput).toContain(
|
||
// TODO: isNextDev ? 'UnnamedError: rsc-error-log-custom-name-Foo' : '[Error]: rsc-error-log-custom-name-Foo'
|
||
isNextDev
|
||
? 'Error: rsc-error-log-custom-name-Foo'
|
||
: 'Error: rsc-error-log-custom-name-Foo'
|
||
)
|
||
})
|
||
|
||
expect(cliOutput).toContain(
|
||
// TODO: isNextDev ? 'NamedError [MyError]: rsc-error-log-custom-name-Bar' : '[MyError]: rsc-error-log-custom-name-Bar'
|
||
isNextDev
|
||
? 'Error [MyError]: rsc-error-log-custom-name-Bar'
|
||
: 'Error [MyError]: rsc-error-log-custom-name-Bar'
|
||
)
|
||
})
|
||
|
||
it('handles invalid sourcemaps gracefully', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
await next.render('/bad-sourcemap')
|
||
|
||
await retry(() => {
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'Error: bad-sourcemap'
|
||
)
|
||
})
|
||
if (isTurbopack) {
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
// Node.js is fine with invalid URLs in index maps apparently.
|
||
'' +
|
||
'Error: bad-sourcemap' +
|
||
'\n at logError (app/bad-sourcemap/custom:/[badhost]/app/bad-sourcemap/page.js:6:17)' +
|
||
'\n at Page (app/bad-sourcemap/custom:/[badhost]/app/bad-sourcemap/page.js:10:3)' +
|
||
'\n'
|
||
)
|
||
} else {
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
// Node.js is not fine with invalid URLs in vanilla source maps.
|
||
// Feel free to adjust these locations. They're just here to showcase
|
||
// sourcemapping is broken on invalid sources.
|
||
'' +
|
||
`\nwebpack-internal:///(rsc)/./app/bad-sourcemap/page.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: TypeError [ERR_INVALID_ARG_TYPE]: The "payload" argument must be of type object. Received null` +
|
||
'\nError: bad-sourcemap' +
|
||
'\n at logError (webpack-internal:///(rsc)/./app/bad-sourcemap/page.js:12:19)' +
|
||
'\n at Page (webpack-internal:///(rsc)/./app/bad-sourcemap/page.js:15:5)'
|
||
)
|
||
// Expect the invalid sourcemap warning only once per render.
|
||
// Dynamic I/O renders three times.
|
||
// One from filterStackFrameDEV.
|
||
// One from findSourceMapURLDEV.
|
||
expect(
|
||
normalizeCliOutput(next.cliOutput.slice(outputIndex)).split(
|
||
'Invalid source map.'
|
||
).length - 1
|
||
).toEqual(3)
|
||
}
|
||
} else {
|
||
// Bundlers silently drop invalid sourcemaps.
|
||
expect(
|
||
normalizeCliOutput(next.cliOutput).split('Invalid source map.').length -
|
||
1
|
||
).toEqual(0)
|
||
}
|
||
})
|
||
|
||
it('sourcemaps errors during module evaluation', async () => {
|
||
const outputIndex = next.cliOutput.length
|
||
const browser = await next.browser('/module-evaluation')
|
||
|
||
if (isNextDev) {
|
||
await retry(() => {
|
||
expect(next.cliOutput.slice(outputIndex)).toContain(
|
||
'Error: module-evaluation'
|
||
)
|
||
})
|
||
const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
|
||
if (isTurbopack) {
|
||
expect(cliOutput).toContain(
|
||
'Error: module-evaluation' +
|
||
// TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
|
||
'\n at module evaluation (app/module-evaluation/module.js:1:22)' +
|
||
// TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
|
||
'\n at module evaluation (app/module-evaluation/page.js:1:1)' +
|
||
'\n at module evaluation (.next'
|
||
)
|
||
} else {
|
||
expect(cliOutput).toContain(
|
||
'Error: module-evaluation' +
|
||
// TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
|
||
// TODO(veil): Location should be sourcemapped
|
||
'\n at eval (app/module-evaluation/module.js:1:22)' +
|
||
// TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
|
||
'\n at <unknown> (rsc)/.'
|
||
)
|
||
}
|
||
|
||
expect(cliOutput).toContain(
|
||
'' +
|
||
"\n> 1 | export const error = new Error('module-evaluation')" +
|
||
'\n | ^'
|
||
)
|
||
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"description": "module-evaluation",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/module-evaluation/module.js (1:22) @ module evaluation
|
||
> 1 | export const error = new Error('module-evaluation')
|
||
| ^",
|
||
"stack": [
|
||
"module evaluation app/module-evaluation/module.js (1:22)",
|
||
"module evaluation app/module-evaluation/page.js (1:1)",
|
||
"module evaluation app/module-evaluation/page.js (6:1)",
|
||
"Page <anonymous>",
|
||
],
|
||
}
|
||
`)
|
||
} else if (isRspack) {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"description": "module-evaluation",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/module-evaluation/module.js (1:22) @ eval
|
||
> 1 | export const error = new Error('module-evaluation')
|
||
| ^",
|
||
"stack": [
|
||
"eval app/module-evaluation/module.js (1:22)",
|
||
"<FIXME-file-protocol>",
|
||
"<FIXME-file-protocol>",
|
||
"eval about:/Prerender/webpack-internal:///(rsc)/app/module-evaluation/page.js (5:60)",
|
||
"<FIXME-file-protocol>",
|
||
"<FIXME-file-protocol>",
|
||
"Function.all <anonymous>",
|
||
"Function.all <anonymous>",
|
||
"Page <anonymous>",
|
||
],
|
||
}
|
||
`)
|
||
} else {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
{
|
||
"description": "module-evaluation",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/module-evaluation/module.js (1:22) @ eval
|
||
> 1 | export const error = new Error('module-evaluation')
|
||
| ^",
|
||
"stack": [
|
||
"eval app/module-evaluation/module.js (1:22)",
|
||
"<FIXME-file-protocol>",
|
||
"eval about:/Prerender/webpack-internal:///(rsc)/app/module-evaluation/page.js (5:65)",
|
||
"<FIXME-file-protocol>",
|
||
"Page <anonymous>",
|
||
],
|
||
}
|
||
`)
|
||
}
|
||
} else {
|
||
if (isTurbopack) {
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
'\nError: module-evaluation' +
|
||
// TODO(veil): Turbopack internals. Feel free to update. Tracked in https://linear.app/vercel/issue/NEXT-4362
|
||
'\n at module evaluation (app/module-evaluation/module.js:1:22)'
|
||
)
|
||
expect(normalizeCliOutput(next.cliOutput)).toContain(
|
||
'' +
|
||
"\n> 1 | export const error = new Error('module-evaluation')" +
|
||
'\n | ^'
|
||
)
|
||
} else {
|
||
expect(
|
||
normalizeCliOutput(next.cliOutput).replaceAll(
|
||
/at \d+ /g,
|
||
'at <WebpackModuleID> '
|
||
)
|
||
).toContain(
|
||
'' +
|
||
'\nError: module-evaluation' +
|
||
// TODO(veil): column numbers are flaky in Webpack
|
||
'\n at <WebpackModuleID> (bundler:///app/module-evaluation/module.js:1:'
|
||
)
|
||
}
|
||
}
|
||
})
|
||
|
||
it('ignore-lists anonymous rsc stack frame sandwiches', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
const browser = await next.browser('/rsc-anonymous-stack-frame-sandwich')
|
||
|
||
// TODO(veil): Implement sandwich heuristic in `filterStackFrameDEV`
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
[
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: external",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
|
||
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
|
||
| ^",
|
||
"stack": [
|
||
"Set.forEach <anonymous>",
|
||
"Set.forEach <anonymous>",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: internal",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
|
||
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
|
||
| ^",
|
||
"stack": [
|
||
"Set.forEach <anonymous>",
|
||
"Set.forEach <anonymous>",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
} else if (isRspack) {
|
||
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
[
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: external",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
|
||
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
|
||
| ^",
|
||
"stack": [
|
||
"Set.forEach <anonymous>",
|
||
"Set.forEach <anonymous>",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: internal",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
|
||
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
|
||
| ^",
|
||
"stack": [
|
||
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (18:54)",
|
||
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (12:7)",
|
||
"Set.forEach <anonymous>",
|
||
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (11:9)",
|
||
"Set.forEach <anonymous>",
|
||
"runSetOfSets webpack-internal:/(ssr)/internal-pkg/ignored.ts (10:13)",
|
||
"runHiddenSetOfSets webpack-internal:/(ssr)/internal-pkg/ignored.ts (18:3)",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
} else {
|
||
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
[
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: external",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
|
||
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
|
||
| ^",
|
||
"stack": [
|
||
"Set.forEach <anonymous>",
|
||
"Set.forEach <anonymous>",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
{
|
||
"description": "rsc-anonymous-stack-frame-sandwich: internal",
|
||
"environmentLabel": "Prerender",
|
||
"label": "Console Error",
|
||
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
|
||
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
|
||
| ^",
|
||
"stack": [
|
||
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (18:54)",
|
||
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (12:7)",
|
||
"Set.forEach <anonymous>",
|
||
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (11:9)",
|
||
"Set.forEach <anonymous>",
|
||
"runSetOfSets webpack-internal:/(rsc)/internal-pkg/ignored.ts (10:13)",
|
||
"runHiddenSetOfSets webpack-internal:/(rsc)/internal-pkg/ignored.ts (18:3)",
|
||
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
|
||
"Page <anonymous>",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
}
|
||
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'' +
|
||
'Error: rsc-anonymous-stack-frame-sandwich: external' +
|
||
'\n at Page (app/rsc-anonymous-stack-frame-sandwich/page.js:5:29)' +
|
||
'\n 3 |' +
|
||
'\n 4 | export default function Page() {' +
|
||
"\n> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')" +
|
||
'\n | ^'
|
||
)
|
||
// TODO: assert on 2nd error once that's bug free
|
||
} else {
|
||
// TODO(veil): assert on 1st error once cursor position is consistent
|
||
// TODO(veil): assert on 2nd error once that's bug free
|
||
}
|
||
})
|
||
|
||
it('ignore-lists anonymous ssr stack frame sandwiches', async () => {
|
||
if (isNextDev) {
|
||
const outputIndex = next.cliOutput.length
|
||
const browser = await next.browser('/ssr-anonymous-stack-frame-sandwich')
|
||
|
||
if (isTurbopack) {
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
[
|
||
{
|
||
"description": "ssr-anonymous-stack-frame-sandwich: external",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
|
||
> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (6:29)",
|
||
],
|
||
},
|
||
{
|
||
"description": "ignore-listed frames",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (7:29) @ Page
|
||
> 7 | runHiddenSetOfSetsInternal('ssr-anonymous-stack-frame-sandwich: internal')
|
||
| ^",
|
||
"stack": [
|
||
"<unknown> internal-pkg/sourcemapped.ts (18:43)",
|
||
"<unknown> internal-pkg/sourcemapped.ts (11:7)",
|
||
"Set.forEach <anonymous>",
|
||
"<unknown> internal-pkg/sourcemapped.ts (10:9)",
|
||
"Set.forEach <anonymous>",
|
||
"runSetOfSets internal-pkg/sourcemapped.ts (9:13)",
|
||
"runHiddenSetOfSets internal-pkg/sourcemapped.ts (17:3)",
|
||
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (7:29)",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
} else {
|
||
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
|
||
await expect(browser).toDisplayCollapsedRedbox(`
|
||
[
|
||
{
|
||
"description": "ssr-anonymous-stack-frame-sandwich: external",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
|
||
> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')
|
||
| ^",
|
||
"stack": [
|
||
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (6:29)",
|
||
],
|
||
},
|
||
{
|
||
"description": "ignore-listed frames",
|
||
"environmentLabel": null,
|
||
"label": "Console Error",
|
||
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (7:29) @ Page
|
||
> 7 | runHiddenSetOfSetsInternal('ssr-anonymous-stack-frame-sandwich: internal')
|
||
| ^",
|
||
"stack": [
|
||
"eval sourcemapped.ts (18:43)",
|
||
"eval sourcemapped.ts (11:7)",
|
||
"Set.forEach <anonymous>",
|
||
"eval sourcemapped.ts (10:9)",
|
||
"Set.forEach <anonymous>",
|
||
"runSetOfSets sourcemapped.ts (9:13)",
|
||
"runHiddenSetOfSets sourcemapped.ts (17:3)",
|
||
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (7:29)",
|
||
],
|
||
},
|
||
]
|
||
`)
|
||
}
|
||
|
||
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
|
||
'' +
|
||
'Error: ssr-anonymous-stack-frame-sandwich: external' +
|
||
'\n at Page (app/ssr-anonymous-stack-frame-sandwich/page.js:6:29)' +
|
||
'\n 4 |' +
|
||
'\n 5 | export default function Page() {' +
|
||
"\n> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')" +
|
||
'\n | ^'
|
||
)
|
||
// TODO(veil): assert on 2nd error once that's bug free
|
||
} else {
|
||
// TODO(veil): assert on 1st error once cursor position is consistent
|
||
// TODO(veil): assert on 2nd error once that's bug free
|
||
}
|
||
})
|
||
})
|