Skip to content

Commit 0f643b8

Browse files
committed
add global error comp and sentry
1 parent ac8d48c commit 0f643b8

File tree

23 files changed

+1781
-69
lines changed

23 files changed

+1781
-69
lines changed

frontend/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ node_modules/
4646
/playwright-report/
4747
/blob-report/
4848
/playwright/.cache/
49+
50+
# Sentry Config File
51+
.env.sentry-build-plugin

frontend/__tests__/unit/pages/Contribute.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('Contribute Component', () => {
110110

111111
test('handles pagination for first page', async () => {
112112
;(fetchAlgoliaData as jest.Mock).mockResolvedValue({
113-
...mockContributeData,
113+
hits: mockContributeData.issues,
114114
totalPages: 2,
115115
currentPage: 1,
116116
})
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { addToast } from '@heroui/toast'
2+
import * as Sentry from '@sentry/nextjs'
3+
import { screen, fireEvent } from '@testing-library/react'
4+
import { useRouter } from 'next/navigation'
5+
import { render } from 'wrappers/testUtil'
6+
import {
7+
ErrorDisplay,
8+
handleAppError,
9+
AppError,
10+
ErrorWrapper,
11+
ERROR_CONFIGS,
12+
} from 'app/global-error'
13+
import GlobalError from 'app/global-error'
14+
15+
// Mocks
16+
jest.mock('next/navigation', () => ({
17+
useRouter: jest.fn(),
18+
}))
19+
jest.mock('@sentry/nextjs', () => ({
20+
captureException: jest.fn(),
21+
ErrorBoundary: ({ _fallback, children }) => <>{children}</>,
22+
}))
23+
jest.mock('@heroui/toast', () => ({
24+
addToast: jest.fn(),
25+
}))
26+
27+
describe('ErrorDisplay Component', () => {
28+
test('renders correct error details', () => {
29+
render(<ErrorDisplay statusCode={404} title="Not Found" message="Page not found" />)
30+
31+
expect(screen.getByText('404')).toBeInTheDocument()
32+
expect(screen.getByText('Not Found')).toBeInTheDocument()
33+
expect(screen.getByText('Page not found')).toBeInTheDocument()
34+
})
35+
36+
test('navigates to home on button press', () => {
37+
const pushMock = jest.fn()
38+
;(useRouter as jest.Mock).mockReturnValue({ push: pushMock })
39+
40+
render(<ErrorDisplay {...ERROR_CONFIGS['404']} />)
41+
42+
fireEvent.click(screen.getByText('Return To Home'))
43+
expect(pushMock).toHaveBeenCalledWith('/')
44+
})
45+
})
46+
47+
describe('handleAppError Function', () => {
48+
beforeEach(() => {
49+
jest.clearAllMocks()
50+
})
51+
52+
test('handles AppError without Sentry for 404', () => {
53+
const error = new AppError(404, 'Page not found')
54+
handleAppError(error)
55+
56+
expect(Sentry.captureException).not.toHaveBeenCalled()
57+
expect(addToast).toHaveBeenCalledWith(
58+
expect.objectContaining({
59+
title: 'Page Not Found',
60+
description: 'Page not found',
61+
})
62+
)
63+
})
64+
65+
test('handles unknown error and calls Sentry for 500+', () => {
66+
handleAppError('Unknown crash')
67+
68+
expect(Sentry.captureException).toHaveBeenCalled()
69+
expect(addToast).toHaveBeenCalledWith(
70+
expect.objectContaining({
71+
description: 'An unexpected server error occurred.',
72+
shouldShowTimeoutProgress: true,
73+
timeout: 5000,
74+
title: 'Server Error',
75+
})
76+
)
77+
})
78+
79+
test('handles normal Error instance', () => {
80+
const err = new Error('Oops')
81+
handleAppError(err)
82+
83+
expect(Sentry.captureException).toHaveBeenCalledWith(err)
84+
expect(addToast).toHaveBeenCalledWith(
85+
expect.objectContaining({
86+
description: 'Oops',
87+
})
88+
)
89+
})
90+
})
91+
92+
describe('AppError class', () => {
93+
test('should extend Error properly', () => {
94+
const err = new AppError(403, 'Forbidden')
95+
expect(err).toBeInstanceOf(Error)
96+
expect(err.name).toBe('AppError')
97+
expect(err.message).toBe('Forbidden')
98+
expect(err.statusCode).toBe(403)
99+
})
100+
})
101+
102+
describe('GlobalError component', () => {
103+
test('renders 500 fallback and calls Sentry', () => {
104+
const error = new Error('Critical failure')
105+
render(<GlobalError error={error} />)
106+
107+
expect(Sentry.captureException).toHaveBeenCalledWith(error)
108+
expect(screen.getByText('Server Error')).toBeInTheDocument()
109+
})
110+
})
111+
112+
describe('ErrorWrapper component', () => {
113+
test('renders children without crashing', () => {
114+
render(
115+
<ErrorWrapper>
116+
<div>App Content</div>
117+
</ErrorWrapper>
118+
)
119+
120+
expect(screen.getByText('App Content')).toBeInTheDocument()
121+
})
122+
})

frontend/jest.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ const config: Config = {
99
'!src/app/layout.tsx',
1010
'!src/components/**',
1111
'!src/hooks/**',
12+
'!src/instrumentation.ts',
13+
'!src/instrumentation-client.ts',
1214
'!src/reportWebVitals.ts',
13-
'!src/sentry.config.ts',
15+
'!src/sentry.server.config.ts',
1416
'!src/server/**',
1517
'!src/setupTests.ts',
1618
'!src/types/**',

frontend/next.config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { withSentryConfig } from '@sentry/nextjs'
12
import type { NextConfig } from 'next'
23

34
const nextConfig: NextConfig = {
45
images: {
6+
// This is a list of remote patterns that Next.js will use to determine if an image is allowed to be loaded from a remote source.
57
remotePatterns: [
68
{
79
protocol: 'https',
@@ -25,4 +27,13 @@ const nextConfig: NextConfig = {
2527
output: 'standalone',
2628
}
2729

28-
export default nextConfig
30+
export default withSentryConfig(nextConfig, {
31+
// For all available options, see:
32+
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
33+
org: 'OWASP',
34+
project: 'Nest',
35+
// Upload a larger set of source maps for prettier stack traces (increases build time)
36+
widenClientFileUpload: true,
37+
//suppress errors
38+
disableLogger: false,
39+
})

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@heroui/toast": "^2.0.7",
3131
"@heroui/tooltip": "^2.2.14",
3232
"@next/eslint-plugin-next": "^15.3.0",
33-
"@sentry/react": "^9.12.0",
33+
"@sentry/nextjs": "^9.12.0",
3434
"@testing-library/user-event": "^14.6.1",
3535
"@types/leaflet.markercluster": "^1.5.5",
3636
"@types/lodash": "^4.17.16",

0 commit comments

Comments
 (0)