Skip to content

Commit 6520334

Browse files
committed
Add unit tests
1 parent 43b79af commit 6520334

File tree

4 files changed

+1537
-319
lines changed

4 files changed

+1537
-319
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { startBrowserTracingNavigationSpan } from '@sentry/browser';
5+
import type { Client, Span } from '@sentry/core';
6+
import { addNonEnumerableProperty } from '@sentry/core';
7+
import * as React from 'react';
8+
import { beforeEach, describe, expect, it, vi } from 'vitest';
9+
import {
10+
addResolvedRoutesToParent,
11+
createNewNavigationSpan,
12+
createReactRouterV6CompatibleTracingIntegration,
13+
handleExistingNavigationSpan,
14+
updateNavigationSpan,
15+
} from '../../src/reactrouter-compat-utils';
16+
import type { Location, RouteObject } from '../../src/types';
17+
18+
const mockUpdateName = vi.fn();
19+
const mockSetAttribute = vi.fn();
20+
const mockSpan = { updateName: mockUpdateName, setAttribute: mockSetAttribute } as unknown as Span;
21+
const mockClient = { addIntegration: vi.fn() } as unknown as Client;
22+
23+
vi.mock('@sentry/core', async requireActual => {
24+
const actual = await requireActual();
25+
return {
26+
...(actual as any),
27+
addNonEnumerableProperty: vi.fn(),
28+
getActiveSpan: vi.fn(() => mockSpan),
29+
getClient: vi.fn(() => mockClient),
30+
getRootSpan: vi.fn(() => mockSpan),
31+
spanToJSON: vi.fn(() => ({ op: 'navigation' })),
32+
};
33+
});
34+
35+
vi.mock('@sentry/browser', async requireActual => {
36+
const actual = await requireActual();
37+
return {
38+
...(actual as any),
39+
startBrowserTracingNavigationSpan: vi.fn(),
40+
browserTracingIntegration: vi.fn(() => ({
41+
setup: vi.fn(),
42+
afterAllSetup: vi.fn(),
43+
name: 'BrowserTracing',
44+
})),
45+
};
46+
});
47+
48+
vi.mock('../../src/reactrouter-compat-utils/utils', () => ({
49+
resolveRouteNameAndSource: vi.fn(() => ['Test Route', 'route']),
50+
initializeRouterUtils: vi.fn(),
51+
getGlobalLocation: vi.fn(() => ({ pathname: '/test', search: '', hash: '' })),
52+
getGlobalPathname: vi.fn(() => '/test'),
53+
}));
54+
55+
vi.mock('../../src/reactrouter-compat-utils/lazy-routes', () => ({
56+
checkRouteForAsyncHandler: vi.fn(),
57+
}));
58+
59+
describe('reactrouter-compat-utils/instrumentation', () => {
60+
const sampleLocation: Location = {
61+
pathname: '/test',
62+
search: '',
63+
hash: '',
64+
state: null,
65+
key: 'default',
66+
};
67+
68+
const sampleRoutes: RouteObject[] = [
69+
{ path: '/', element: <div>Home</div> },
70+
{ path: '/about', element: <div>About</div> },
71+
];
72+
73+
const mockMatchRoutes = vi.fn(() => []);
74+
75+
beforeEach(() => {
76+
vi.clearAllMocks();
77+
});
78+
79+
describe('updateNavigationSpan', () => {
80+
it('should update navigation span name and source when not already named', () => {
81+
updateNavigationSpan(mockSpan, sampleLocation, sampleRoutes, false, mockMatchRoutes);
82+
83+
expect(mockUpdateName).toHaveBeenCalledWith('Test Route');
84+
expect(mockSetAttribute).toHaveBeenCalledWith('sentry.source', 'route');
85+
expect(addNonEnumerableProperty).toHaveBeenCalledWith(mockSpan, '__sentry_navigation_name_set__', true);
86+
});
87+
88+
it('should not update when span already has name set', () => {
89+
const spanWithNameSet = { ...mockSpan, __sentry_navigation_name_set__: true };
90+
91+
updateNavigationSpan(spanWithNameSet as any, sampleLocation, sampleRoutes, false, mockMatchRoutes);
92+
93+
expect(mockUpdateName).not.toHaveBeenCalled();
94+
});
95+
});
96+
97+
describe('handleExistingNavigationSpan', () => {
98+
it('should update span name when not already named', () => {
99+
const spanJson = { op: 'navigation', data: {}, span_id: 'test', start_timestamp: 0, trace_id: 'test' };
100+
101+
handleExistingNavigationSpan(mockSpan, spanJson, 'Test Route', 'route', false);
102+
103+
expect(mockUpdateName).toHaveBeenCalledWith('Test Route');
104+
expect(mockSetAttribute).toHaveBeenCalledWith('sentry.source', 'route');
105+
expect(addNonEnumerableProperty).toHaveBeenCalledWith(mockSpan, '__sentry_navigation_name_set__', true);
106+
});
107+
108+
it('should not mark as named for lazy routes', () => {
109+
const spanJson = { op: 'navigation', data: {}, span_id: 'test', start_timestamp: 0, trace_id: 'test' };
110+
111+
handleExistingNavigationSpan(mockSpan, spanJson, 'Test Route', 'route', true);
112+
113+
expect(mockUpdateName).toHaveBeenCalledWith('Test Route');
114+
expect(mockSetAttribute).toHaveBeenCalledWith('sentry.source', 'route');
115+
expect(addNonEnumerableProperty).not.toHaveBeenCalled();
116+
});
117+
});
118+
119+
describe('createNewNavigationSpan', () => {
120+
it('should create new navigation span with correct attributes', () => {
121+
createNewNavigationSpan(mockClient, 'Test Route', 'route', '6', false);
122+
123+
expect(startBrowserTracingNavigationSpan).toHaveBeenCalledWith(mockClient, {
124+
name: 'Test Route',
125+
attributes: {
126+
'sentry.source': 'route',
127+
'sentry.op': 'navigation',
128+
'sentry.origin': 'auto.navigation.react.reactrouter_v6',
129+
},
130+
});
131+
});
132+
});
133+
134+
describe('addResolvedRoutesToParent', () => {
135+
it('should add new routes to parent with no existing children', () => {
136+
const parentRoute: RouteObject = { path: '/parent', element: <div>Parent</div> };
137+
const resolvedRoutes = [{ path: '/child1', element: <div>Child 1</div> }];
138+
139+
addResolvedRoutesToParent(resolvedRoutes, parentRoute);
140+
141+
expect(parentRoute.children).toEqual(resolvedRoutes);
142+
});
143+
144+
it('should not add duplicate routes by path', () => {
145+
const existingRoute = { path: '/duplicate', element: <div>Existing</div> };
146+
const parentRoute: RouteObject = {
147+
path: '/parent',
148+
element: <div>Parent</div>,
149+
children: [existingRoute],
150+
};
151+
const duplicateRoute = { path: '/duplicate', element: <div>Duplicate</div> };
152+
153+
addResolvedRoutesToParent([duplicateRoute], parentRoute);
154+
155+
expect(parentRoute.children).toEqual([existingRoute]);
156+
});
157+
});
158+
159+
describe('createReactRouterV6CompatibleTracingIntegration', () => {
160+
it('should create integration with correct setup', () => {
161+
const mockUseEffect = vi.fn();
162+
const mockUseLocation = vi.fn();
163+
const mockUseNavigationType = vi.fn();
164+
const mockCreateRoutesFromChildren = vi.fn();
165+
166+
const integration = createReactRouterV6CompatibleTracingIntegration(
167+
{
168+
useEffect: mockUseEffect,
169+
useLocation: mockUseLocation,
170+
useNavigationType: mockUseNavigationType,
171+
createRoutesFromChildren: mockCreateRoutesFromChildren,
172+
matchRoutes: mockMatchRoutes,
173+
},
174+
'6',
175+
);
176+
177+
expect(integration).toHaveProperty('setup');
178+
expect(integration).toHaveProperty('afterAllSetup');
179+
expect(typeof integration.setup).toBe('function');
180+
expect(typeof integration.afterAllSetup).toBe('function');
181+
});
182+
});
183+
});

0 commit comments

Comments
 (0)