Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint-local-rules/disallowSideEffects.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const pathsWithSideEffect = new Set([
const packagesWithoutSideEffect = new Set([
'@datadog/browser-core',
'@datadog/browser-rum-core',
'@datadog/browser-rum-react/internal',
'react',
'react-router-dom',
'vue',
Expand Down
6 changes: 4 additions & 2 deletions packages/rum-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"dependencies": {
"@datadog/browser-core": "6.31.0",
"@datadog/browser-rum-core": "6.31.0"
"@datadog/browser-rum-core": "6.31.0",
"@datadog/browser-rum-react": "6.31.0"
},
"peerDependencies": {
"next": ">=13.0.0",
Expand All @@ -36,6 +37,7 @@
"devDependencies": {
"@types/react": "19.2.11",
"next": "15.5.10",
"react": "19.2.4"
"react": "19.2.4",
"react-dom": "19.2.4"
}
}
102 changes: 102 additions & 0 deletions packages/rum-nextjs/src/domain/error/errorBoundary.spec.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all these tests are the same for rum-react (except for the last one) should I remove them?

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { act } from 'react'

import {
disableJasmineUncaughtExceptionTracking,
ignoreConsoleLogs,
registerCleanupTask,
} from '@datadog/browser-core/test'
import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core'
import { appendComponent } from '../../../../rum-react/test/appendComponent'
import { initReactOldBrowsersSupport } from '../../../../rum-react/test/reactOldBrowsersSupport'
import { nextjsPlugin, resetNextjsPlugin } from '../nextjsPlugin'
import type { ErrorBoundaryFallback } from './errorBoundary'
import { ErrorBoundary } from './errorBoundary'

type FallbackFunctionComponent = Extract<ErrorBoundaryFallback, (...args: any[]) => any>

function initializeNextjsPlugin() {
const addErrorSpy = jasmine.createSpy()
const publicApi = { startView: jasmine.createSpy() } as unknown as RumPublicApi
const plugin = nextjsPlugin()
plugin.onInit({ publicApi, initConfiguration: {} as RumInitConfiguration })
plugin.onRumStart({ addError: addErrorSpy })
return { addErrorSpy }
}

describe('NextjsErrorBoundary', () => {
beforeEach(() => {
ignoreConsoleLogs('error', 'Error: error')
disableJasmineUncaughtExceptionTracking()
initReactOldBrowsersSupport()
resetNextjsPlugin()
registerCleanupTask(() => resetNextjsPlugin())
})

it('renders children', () => {
const container = appendComponent(<ErrorBoundary fallback={() => null}>bar</ErrorBoundary>)
expect(container.innerHTML).toBe('bar')
})

it('renders the fallback when an error occurs', () => {
const fallbackSpy = jasmine.createSpy<FallbackFunctionComponent>().and.returnValue('fallback')
const ComponentSpy = jasmine.createSpy().and.throwError(new Error('error'))
const container = appendComponent(
<ErrorBoundary fallback={fallbackSpy}>
<ComponentSpy />
</ErrorBoundary>
)

expect(fallbackSpy).toHaveBeenCalled()
fallbackSpy.calls.all().forEach(({ args }) => {
expect(args[0]).toEqual({
error: new Error('error'),
resetError: jasmine.any(Function),
})
})
expect(container.innerHTML).toBe('fallback')
})

it('resets the error when resetError is called', () => {
const fallbackSpy = jasmine.createSpy<FallbackFunctionComponent>().and.returnValue('fallback')
const ComponentSpy = jasmine.createSpy().and.throwError(new Error('error'))
const container = appendComponent(
<ErrorBoundary fallback={fallbackSpy}>
<ComponentSpy />
</ErrorBoundary>
)

ComponentSpy.and.returnValue('bar')

const { resetError } = fallbackSpy.calls.mostRecent().args[0]
act(() => {
resetError()
})

expect(container.innerHTML).toBe('bar')
})

it('reports the error through addNextjsError', () => {
const { addErrorSpy } = initializeNextjsPlugin()
const originalError = new Error('error')
const ComponentSpy = jasmine.createSpy().and.throwError(originalError)
;(ComponentSpy as any).displayName = 'ComponentSpy'

appendComponent(
<ErrorBoundary fallback={() => null}>
<ComponentSpy />
</ErrorBoundary>
)

expect(addErrorSpy).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
error: originalError,
handlingStack: jasmine.any(String),
startClocks: jasmine.any(Object),
context: jasmine.objectContaining({
framework: 'nextjs',
}),
componentStack: jasmine.stringContaining('ComponentSpy'),
})
)
})
})
22 changes: 22 additions & 0 deletions packages/rum-nextjs/src/domain/error/errorBoundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'
import { createErrorBoundary } from '@datadog/browser-rum-react/internal'
export type { ErrorBoundaryFallback, ErrorBoundaryProps } from '@datadog/browser-rum-react/internal'
import { addNextjsError } from './addNextjsError'

/**
* ErrorBoundary component to report React errors to Datadog using the Next.js error context.
*
* For more advanced error handling, you can use the {@link addNextjsError} function.
*
* @category Error
* @example
* ```ts
* import { ErrorBoundary } from '@datadog/browser-rum-nextjs'
*
* <ErrorBoundary fallback={() => null}>
* <Component />
* </ErrorBoundary>
* ```
*/
// eslint-disable-next-line local-rules/disallow-side-effects
export const ErrorBoundary = createErrorBoundary(addNextjsError)
2 changes: 2 additions & 0 deletions packages/rum-nextjs/src/entries/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export type { NextjsPlugin } from '../domain/nextjsPlugin'
export { DatadogAppRouter } from '../domain/nextJSRouter/datadogAppRouter'
export { DatadogPagesRouter } from '../domain/nextJSRouter/datadogPagesRouter'
export { addNextjsError } from '../domain/error/addNextjsError'
export { ErrorBoundary } from '../domain/error/errorBoundary'
export type { ErrorBoundaryFallback, ErrorBoundaryProps } from '../domain/error/errorBoundary'
1 change: 1 addition & 0 deletions packages/rum-react/internal/internal.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/entries/internal'
2 changes: 1 addition & 1 deletion packages/rum-react/internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"private": true,
"main": "../cjs/entries/internal.js",
"module": "../esm/entries/internal.js",
"types": "../cjs/entries/internal.d.ts"
"types": "./internal.d.ts"
}
2 changes: 1 addition & 1 deletion test/apps/nextjs/app/error-test/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function ErrorBoundary({ error, reset }: { error: Error & { diges
{error.digest && <p data-testid="error-digest">Digest: {error.digest}</p>}
<button onClick={reset}>Try again</button>
<br />
<Link href="/">Go to Home</Link>
<Link href="/">Back to Home</Link>
</div>
)
}
2 changes: 1 addition & 1 deletion test/apps/nextjs/app/error-test/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function ErrorTestPage() {

return (
<div>
<Link href="/">Back to Home</Link>
<Link href="/">Back to Home</Link>
<h1>Error Test</h1>
<button data-testid="trigger-error" onClick={() => setShouldThrow(true)}>
Trigger Error
Expand Down
2 changes: 1 addition & 1 deletion test/apps/nextjs/app/guides/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default async function GuidesPage({ params }: { params: Promise<{ slug: s

return (
<div>
<Link href="/">Back to Home</Link>
<Link href="/">Back to Home</Link>
<h1>Guides: {slug.join('/')}</h1>
<p>This is a catch-all route testing slug normalization.</p>
</div>
Expand Down
3 changes: 1 addition & 2 deletions test/apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
"name": "nextjs",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start --port 0"
},
"dependencies": {
"@datadog/browser-rum": "file:../../../packages/rum/package.tgz",
Expand Down
35 changes: 35 additions & 0 deletions test/apps/nextjs/pages/pages-router/error-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ErrorBoundary } from '@datadog/browser-rum-nextjs'
import type { ErrorBoundaryFallback } from '@datadog/browser-rum-nextjs'
import { useState } from 'react'
import Link from 'next/link'

function ErrorThrower() {
const [shouldThrow, setShouldThrow] = useState(false)
if (shouldThrow) throw new Error('Pages Router error from NextjsErrorBoundary')
return (
<button data-testid="trigger-error" onClick={() => setShouldThrow(true)}>
Trigger Error
</button>
)
}

const ErrorFallback: ErrorBoundaryFallback = ({ error, resetError }) => (
<div data-testid="error-boundary">
<p>{error.message}</p>
<button data-testid="reset-error" onClick={resetError}>
Reset
</button>
</div>
)

export default function ErrorTestPage() {
return (
<div>
<Link href="/pages-router">← Back to Home</Link>
<h1>Error Test</h1>
<ErrorBoundary fallback={ErrorFallback}>
<ErrorThrower />
</ErrorBoundary>
</div>
)
}
3 changes: 3 additions & 0 deletions test/apps/nextjs/pages/pages-router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export default function HomePage() {
<li>
<Link href="/pages-router/guides/123">Go to Guides 123</Link>
</li>
<li>
<Link href="/pages-router/error-test">Go to Error Test</Link>
</li>
</ul>
</div>
)
Expand Down
12 changes: 9 additions & 3 deletions test/apps/nextjs/pages/pages-router/user/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ export default function UserPage() {
<Link href="/pages-router">← Back to Home</Link>
<h1>User {id}</h1>
<p>This is a dynamic route testing view name normalization.</p>
<Link href="/pages-router/user/999?admin=true">Go to User 999</Link>
<Link href={`/pages-router/user/${id}?admin=false`}>Change query params</Link>
<Link href={`/pages-router/user/${id}#section`}>Go to Section</Link>
<div>
<Link href="/pages-router/user/999?admin=true">Go to User 999</Link>
</div>
<div>
<Link href={`/pages-router/user/${id}?admin=false`}>Change query params</Link>
</div>
<div>
<Link href={`/pages-router/user/${id}#section`}>Go to Section</Link>
</div>
</div>
)
}
29 changes: 15 additions & 14 deletions test/apps/nextjs/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ __metadata:

"@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=nextjs%40workspace%3A.":
version: 6.31.0
resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=55eb2b&locator=nextjs%40workspace%3A."
resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=cad31e&locator=nextjs%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.31.0"
checksum: 10c0/11b93076a410ef527c70d94d7b3203a3a24e05b665f40ce9b1c2227a30ee7efd99613e02a139be7f94ee1a95ca48cb2bf83e8b6d9f3d5253f7bf37d89f1ea338
checksum: 10c0/f57c5d9fdbc7243f373afba6912bb75a90f3b65ab036750a858aa7776ed30b8921e456eff32a02e7dc021c192cd7bc220ea7b124459eacb7685c5c5ba0cca1c5
languageName: node
linkType: hard

"@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz::locator=nextjs%40workspace%3A.":
version: 0.0.0
resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=72ad74&locator=nextjs%40workspace%3A."
resolution: "@datadog/browser-rum-nextjs@file:../../../packages/rum-nextjs/package.tgz#../../../packages/rum-nextjs/package.tgz::hash=528334&locator=nextjs%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.31.0"
"@datadog/browser-rum-core": "npm:6.31.0"
"@datadog/browser-rum-react": "npm:6.31.0"
peerDependencies:
next: ">=13.0.0"
react: ">=18.0.0"
Expand All @@ -35,13 +36,13 @@ __metadata:
optional: true
react:
optional: true
checksum: 10c0/4b71f2d2e40522120685bfb2457b17febae3f339ca19bc84d58a35f47da1c7a6a49ec13eaec8d62a7f8d61c4d4611293ff7053e338490a74b6f58263e1bc84e6
checksum: 10c0/08d0f50b13a5e2a32b40e14cb9b7c9e78fabc728bfeb7f172ba1aa0e232989c4ab9a2e6fc9a1194d02aeec4ad259a9448cb3745268bea7c9dcb302d7aae90e50
languageName: node
linkType: hard

"@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz::locator=nextjs%40workspace%3A.":
version: 6.31.0
resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=ff7bfc&locator=nextjs%40workspace%3A."
resolution: "@datadog/browser-rum-react@file:../../../packages/rum-react/package.tgz#../../../packages/rum-react/package.tgz::hash=1edc15&locator=nextjs%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.31.0"
"@datadog/browser-rum-core": "npm:6.31.0"
Expand All @@ -60,13 +61,13 @@ __metadata:
optional: true
react-router-dom:
optional: true
checksum: 10c0/6d2a5f34727c9763abd67aaf9d2ba501e9fd38e8d754a15672e929dc4a37c93c85cdce08ce307122b9d055c688dfee0147315f3679fa377620318b4ab84f00bc
checksum: 10c0/e3ed7ae929dd29836b3f2d7adb910dc9e2fdc6cf6fce81e77e950ed5cd060bb9810c30b8eb8772cf9436203f5c08380b1879454aac856509224bc3a511c9af61
languageName: node
linkType: hard

"@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=nextjs%40workspace%3A.":
version: 6.31.0
resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=b23186&locator=nextjs%40workspace%3A."
resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=c2aa74&locator=nextjs%40workspace%3A."
dependencies:
"@datadog/browser-core": "npm:6.31.0"
"@datadog/browser-rum-core": "npm:6.31.0"
Expand All @@ -75,7 +76,7 @@ __metadata:
peerDependenciesMeta:
"@datadog/browser-logs":
optional: true
checksum: 10c0/5eeaf5251ce0207c45d6782b3e219f8366fbce0b60bddd8fa35473a3dfc84a97fc6e2fb686033d9c985ebade414ef432cd18f17e753c694c469a3e9a6ac4aad5
checksum: 10c0/135a2cdf999ea9505e08031ae7069f4d6a236a88e6203ef7105c784a77ae23a23fbabc648d793305dcb4b960cbe4a535fab2d688748efb5824137597f4de84b5
languageName: node
linkType: hard

Expand Down Expand Up @@ -415,18 +416,18 @@ __metadata:
linkType: hard

"baseline-browser-mapping@npm:^2.8.3":
version: 2.10.7
resolution: "baseline-browser-mapping@npm:2.10.7"
version: 2.10.8
resolution: "baseline-browser-mapping@npm:2.10.8"
bin:
baseline-browser-mapping: dist/cli.cjs
checksum: 10c0/fe2988088ede5a2dc7936f0a718d4b500b2a28a7ee3e11e2a3844a9444dd217a95a070c00508d8130da73c4fe576d677b21844bc078f6cd7867fb0e1be60caf0
checksum: 10c0/a77882e8ac37a900a9a0757b9bf4e50f407829ed17ee7630273ab5da0ae10d9c64ed4c5a1e03afb3490e39713440092417ded4bb856eeb9bd44856eacbd97497
languageName: node
linkType: hard

"caniuse-lite@npm:^1.0.30001579":
version: 1.0.30001778
resolution: "caniuse-lite@npm:1.0.30001778"
checksum: 10c0/830042e0a6af0796d3da4d9575f60966b92308c5504577993b618dd196c835d023dbd725fa8b47c33b74c487d75ce01ee3ebd6e7a078714989513110e8ff80e5
version: 1.0.30001780
resolution: "caniuse-lite@npm:1.0.30001780"
checksum: 10c0/8a88f39758a228852d6f3ac92362ecb7694b1b2b022f194d8dfe59123ad40a5de6202bf2dff0fe316bb3d5ca9caf316c22056e0da693459c3be2771cde4f4bf9
languageName: node
linkType: hard

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/playwright.base.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const config: Config = {
name: 'nextjs app router',
stdout: 'pipe' as const,
cwd: path.join(__dirname, '../apps/nextjs'),
command: isLocal ? 'yarn dev' : 'yarn start',
command: 'yarn start',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for 2 reasons:

  1. So that StrictMode does not double-fires in dev mode.
  2. The dev error overlay does not appear (ex)

wait: {
stdout: /- Local:\s+http:\/\/localhost:(?<nextjs_app_router_port>\d+)/,
},
Expand Down
Loading
Loading