Skip to content

Commit 585dc3d

Browse files
TomWoodwardclaude
andauthored
Update @sentry/react from v7 to v10 (#121)
* Update @sentry/react from v7 to v10 Bumps @sentry/react ^7.120.3 -> ^10.60.0 (most recent), plus the supporting toolchain required by the new major: - sentry-testkit ^5.0.5 -> ^6.4.1 (v8+ compatible) - typescript ^4.7.4 -> ^5.9.3 (Sentry v9+ requires TS >= 5.0.4) Source changes for SDK breaking changes: - ErrorBoundary: import ErrorBoundaryProps from the package root (the internal @sentry/react/types/errorboundary path was removed in v8) and cast the now-`unknown` callback error to Error at the boundary. - ErrorBoundary story: String(props.error) for the now-`unknown` type. Test changes (environment/SDK behavior, not product bugs): - v8+ exposes its exports as read-only getters, breaking jest.spyOn. Mock @sentry/react in the affected specs so spies and the testkit transport keep working. - Add a jest setup file polyfilling performance.getEntriesByType, which v10's default browserTracingIntegration calls on init but jsdom lacks. - Reset the testkit between ErrorBoundary tests; v10 flushes captured events synchronously, exposing cross-test report leakage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * version bump * bump --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c4146b6 commit 585dc3d

9 files changed

Lines changed: 250 additions & 202 deletions

File tree

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
module.exports = {
33
preset: 'ts-jest',
44
testEnvironment: 'jsdom',
5+
setupFilesAfterEnv: ["<rootDir>/src/test/setup.ts"],
56
transform: {
67
"^.+\\.tsx?$": "ts-jest"
78
},

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openstax/ui-components",
3-
"version": "1.22.4",
3+
"version": "1.23.1",
44
"license": "MIT",
55
"sideEffects": [
66
"**/*.css"
@@ -83,13 +83,13 @@
8383
"react-dom": "^17.0.2",
8484
"react-is": "^16.8.0",
8585
"react-test-renderer": "^17.0.2",
86-
"sentry-testkit": "^5.0.5",
86+
"sentry-testkit": "^6.4.1",
8787
"styled-components": "^5.3.5",
8888
"ts-jest": "^28.0.5",
89-
"typescript": "^4.7.4"
89+
"typescript": "^5.9.3"
9090
},
9191
"dependencies": {
92-
"@sentry/react": "^7.120.3",
92+
"@sentry/react": "^10.60.0",
9393
"classnames": "^2.3.1",
9494
"dompurify": "^3.0.1",
9595
"react-aria": "^3.37.0",

src/components/Error.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import { render, act } from '@testing-library/react';
33
import { Error } from './Error';
44
import * as Sentry from '@sentry/react';
55

6+
// Sentry v8+ exposes its named exports as read-only getters, so they can no longer
7+
// be replaced with jest.spyOn directly. Mock the module to make lastEventId spyable.
8+
jest.mock('@sentry/react', () => ({
9+
__esModule: true,
10+
...jest.requireActual('@sentry/react'),
11+
lastEventId: jest.fn(),
12+
}));
13+
614
describe('Error', () => {
715
it('matches snapshot', () => {
816
const tree = renderer.create(

src/components/ErrorBoundary.spec.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ import * as Sentry from '@sentry/react';
55
import { findByTestId } from '../test/utils';
66
import { SessionExpiredError } from '@openstax/ts-utils/errors';
77

8+
// Sentry v8+ exposes its named exports as read-only getters, so they can no longer
9+
// be replaced with jest.spyOn directly. Mock the module to make lastEventId spyable
10+
// while keeping the real init/captureException so the testkit transport still works.
11+
jest.mock('@sentry/react', () => ({
12+
__esModule: true,
13+
...jest.requireActual('@sentry/react'),
14+
lastEventId: jest.fn(),
15+
}));
16+
817
const { testkit, sentryTransport } = sentryTestkit();
918

1019
const ErrorComponent = () => { throw new Error('Test Error') };
@@ -21,6 +30,9 @@ describe('ErrorBoundary', () => {
2130

2231
afterEach(() => {
2332
jest.resetAllMocks();
33+
// Sentry v8+ flushes captured events to the testkit transport synchronously,
34+
// so reports leak between tests unless we clear them after each one.
35+
testkit.reset();
2436
});
2537

2638
it('renders children', () => {

src/components/ErrorBoundary.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const Fallback_GenericError_Custom = () => {
6767
fallback={(props) => (
6868
<>
6969
<h2>This is a custom error fallback</h2>
70-
<p>{props && props.error.toString()}</p>
70+
<p>{props && String(props.error)}</p>
7171
{props && props.resetError ? <button onClick={props && props.resetError}>Reset</button> : null}
7272
</>
7373
)}>

src/components/ErrorBoundary.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Sentry from '@sentry/react';
2+
import type { ErrorBoundaryProps } from '@sentry/react';
23
import React from 'react';
34
import { Error as ErrorComponent, ErrorPropTypes } from './Error';
4-
import type { ErrorBoundaryProps } from '@sentry/react/types/errorboundary';
55
import { ErrorContext } from '../contexts';
66
import { SentryError } from '../types';
77
import { getTypeFromError } from '../utils';
@@ -94,9 +94,11 @@ export const ErrorBoundary = ({
9494
fallback={renderElement}
9595
onError={(error, componentStack, eventId) => {
9696
setError({
97-
error,
97+
// Sentry v8+ types this callback's error as `unknown`; a React error boundary
98+
// always hands us a thrown Error here.
99+
error: error as Error,
98100
// If the error is a custom error from ts-utils, use the custom type instead of 'Error'
99-
type: getTypeFromError(error),
101+
type: getTypeFromError(error as Error),
100102
componentStack,
101103
eventId
102104
});

src/sentryLogger/sentryLog.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import { createSentryLogger } from './sentryLog';
22
import * as Sentry from "@sentry/react";
33
import { Level, Logger } from '@openstax/ts-utils/services/logger';
44

5+
// Sentry v8+ exposes its named exports as read-only getters, so they can no longer
6+
// be replaced with jest.spyOn directly. Mock the module to make addBreadcrumb spyable.
7+
jest.mock('@sentry/react', () => ({
8+
__esModule: true,
9+
...jest.requireActual('@sentry/react'),
10+
addBreadcrumb: jest.fn(),
11+
}));
12+
513
describe('createConsoleLogger', () => {
614
let logFn: jest.SpyInstance;
715
let logger: Logger;

src/test/setup.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// jsdom does not implement parts of the Performance API that the Sentry v8+
2+
// browserTracingIntegration relies on (web-vitals instrumentation calls
3+
// performance.getEntriesByType on init). Provide no-op shims so Sentry.init
4+
// works under the jsdom test environment; real browsers supply these natively.
5+
if (typeof performance.getEntriesByType !== 'function') {
6+
performance.getEntriesByType = () => [];
7+
}
8+
if (typeof performance.getEntriesByName !== 'function') {
9+
performance.getEntriesByName = () => [];
10+
}

0 commit comments

Comments
 (0)