Voeg Sentry-foutregistratie toe als gekozen logging-bestemming

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-07-25 13:15:18 +02:00
co-authored by Junie
parent 7e53d4d7e2
commit 7680feb29f
12 changed files with 190 additions and 13 deletions
+2
View File
@@ -1,5 +1,6 @@
import { Component } from 'react';
import type { ErrorInfo, ReactNode } from 'react';
import * as Sentry from '@sentry/react';
interface ErrorBoundaryProps {
children: ReactNode;
@@ -22,6 +23,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error('Unexpected application error', error, info);
Sentry.captureException(error, { extra: { componentStack: info.componentStack } });
}
render(): ReactNode {
@@ -0,0 +1,61 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as Sentry from '@sentry/react';
import { ErrorBoundary } from '../ErrorBoundary';
vi.mock('@sentry/react', () => ({
captureException: vi.fn(),
}));
function ThrowingChild(): never {
throw new Error('boom');
}
describe('ErrorBoundary', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders children when there is no error', () => {
render(
<ErrorBoundary>
<p>Alles werkt</p>
</ErrorBoundary>,
);
expect(screen.getByText('Alles werkt')).toBeInTheDocument();
});
it('renders the generic fallback message when a child throws', () => {
// Suppress the expected React error boundary console.error noise for this test.
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
render(
<ErrorBoundary>
<ThrowingChild />
</ErrorBoundary>,
);
expect(screen.getByTestId('error-boundary-fallback')).toBeInTheDocument();
expect(screen.getByText('Er ging iets mis. Probeer de pagina te vernieuwen.')).toBeInTheDocument();
consoleErrorSpy.mockRestore();
});
it('reports the caught error to Sentry', () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
render(
<ErrorBoundary>
<ThrowingChild />
</ErrorBoundary>,
);
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
expect(Sentry.captureException).toHaveBeenCalledWith(
expect.any(Error),
expect.objectContaining({ extra: expect.objectContaining({ componentStack: expect.any(String) }) }),
);
consoleErrorSpy.mockRestore();
});
});
+6
View File
@@ -1,10 +1,16 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from '@tanstack/react-router';
import * as Sentry from '@sentry/react';
import './fonts';
import './index.css';
import { router } from './router';
const sentryDsn = import.meta.env.VITE_SENTRY_DSN;
if (sentryDsn) {
Sentry.init({ dsn: sentryDsn });
}
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element "#root" not found in index.html');
+10
View File
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
/** Sentry DSN (Data Source Name) for client-side error reporting. Not a secret — safe to expose in the client bundle. */
readonly VITE_SENTRY_DSN?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}