Skip to content

Commit 46d9f7a

Browse files
author
Sébastien Henau
committed
tests: add router tests
1 parent ef94d11 commit 46d9f7a

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

packages/vue/tests/flareVue.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ vi.mock('@flareapp/js', () => ({
1414
},
1515
}));
1616

17+
function createMockRouter(route: Record<string, unknown>) {
18+
return { currentRoute: { value: route } };
19+
}
20+
1721
function createMockApp(options?: {
1822
errorHandler?: (...args: unknown[]) => void;
1923
warnHandler?: (...args: unknown[]) => void;
24+
router?: ReturnType<typeof createMockRouter>;
2025
}) {
2126
return {
2227
config: {
2328
errorHandler: options?.errorHandler ?? undefined,
2429
warnHandler: options?.warnHandler ?? undefined,
30+
globalProperties: {
31+
$router: options?.router ?? undefined,
32+
} as Record<string, unknown>,
2533
},
2634
};
2735
}
@@ -636,3 +644,81 @@ describe('flareVue captureWarnings', () => {
636644
expect(mockReportMessage.mock.calls[1][0]).toBe('Second warning');
637645
});
638646
});
647+
648+
describe('flareVue route context', () => {
649+
const mockRoute = {
650+
name: 'user-profile',
651+
path: '/users/42',
652+
fullPath: '/users/42?tab=settings',
653+
params: { id: '42' },
654+
query: { tab: 'settings' },
655+
hash: '',
656+
matched: [{ name: 'AppLayout' }, { name: 'UserProfile' }],
657+
};
658+
659+
test('includes route context when Vue Router is present', () => {
660+
const app = createMockApp({ router: createMockRouter(mockRoute) });
661+
(flareVue as Function)(app);
662+
663+
callHandler(app, new Error('test'), null, 'setup function');
664+
665+
const context = mockReport.mock.calls[0][1];
666+
expect(context.vue.route).toEqual({
667+
name: 'user-profile',
668+
path: '/users/42',
669+
fullPath: '/users/42?tab=settings',
670+
params: { id: '42' },
671+
query: { tab: 'settings' },
672+
hash: '',
673+
matched: ['AppLayout', 'UserProfile'],
674+
});
675+
});
676+
677+
test('does not include route context when Vue Router is not present', () => {
678+
const app = createMockApp();
679+
(flareVue as Function)(app);
680+
681+
callHandler(app, new Error('test'), null, 'setup function');
682+
683+
const context = mockReport.mock.calls[0][1];
684+
expect(context.vue.route).toBeUndefined();
685+
});
686+
687+
test('includes route context in warning reports when Vue Router is present', () => {
688+
const app = createMockApp({ router: createMockRouter(mockRoute) });
689+
(flareVue as Function)(app, { captureWarnings: true } satisfies FlareVueOptions);
690+
691+
app.config.warnHandler!('Invalid prop', null, 'trace');
692+
693+
const context = mockReportMessage.mock.calls[0][1];
694+
expect(context.vue.route).toEqual({
695+
name: 'user-profile',
696+
path: '/users/42',
697+
fullPath: '/users/42?tab=settings',
698+
params: { id: '42' },
699+
query: { tab: 'settings' },
700+
hash: '',
701+
matched: ['AppLayout', 'UserProfile'],
702+
});
703+
});
704+
705+
test('does not include route context in warning reports when Vue Router is not present', () => {
706+
const app = createMockApp();
707+
(flareVue as Function)(app, { captureWarnings: true } satisfies FlareVueOptions);
708+
709+
app.config.warnHandler!('Warning', null, '');
710+
711+
const context = mockReportMessage.mock.calls[0][1];
712+
expect(context.vue.route).toBeUndefined();
713+
});
714+
715+
test('route context is available to beforeSubmit hook', () => {
716+
const beforeSubmit = vi.fn(({ context }: { context: FlareVueContext }) => context);
717+
const app = createMockApp({ router: createMockRouter(mockRoute) });
718+
(flareVue as Function)(app, { beforeSubmit } satisfies FlareVueOptions);
719+
720+
callHandler(app, new Error('test'), null, 'setup function');
721+
722+
expect(beforeSubmit.mock.calls[0][0].context.vue.route?.name).toBe('user-profile');
723+
});
724+
});
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { describe, expect, test } from 'vitest';
2+
3+
import { getRouteContext } from '../src/getRouteContext';
4+
5+
function createMockRouter(route: Record<string, unknown>) {
6+
return { currentRoute: { value: route } };
7+
}
8+
9+
describe('getRouteContext', () => {
10+
test('returns null when router is null', () => {
11+
expect(getRouteContext(null)).toBeNull();
12+
});
13+
14+
test('returns null when router is undefined', () => {
15+
expect(getRouteContext(undefined)).toBeNull();
16+
});
17+
18+
test('returns null when router is not an object', () => {
19+
expect(getRouteContext('string')).toBeNull();
20+
expect(getRouteContext(42)).toBeNull();
21+
});
22+
23+
test('returns null when router has no currentRoute', () => {
24+
expect(getRouteContext({})).toBeNull();
25+
});
26+
27+
test('returns null when currentRoute has no value', () => {
28+
expect(getRouteContext({ currentRoute: {} })).toBeNull();
29+
});
30+
31+
test('returns null when currentRoute.value is null', () => {
32+
expect(getRouteContext({ currentRoute: { value: null } })).toBeNull();
33+
});
34+
35+
test('extracts route context from a valid router', () => {
36+
const router = createMockRouter({
37+
name: 'user-profile',
38+
path: '/users/42',
39+
fullPath: '/users/42?tab=settings',
40+
params: { id: '42' },
41+
query: { tab: 'settings' },
42+
hash: '',
43+
matched: [{ name: 'AppLayout' }, { name: 'UserProfile' }],
44+
});
45+
46+
expect(getRouteContext(router)).toEqual({
47+
name: 'user-profile',
48+
path: '/users/42',
49+
fullPath: '/users/42?tab=settings',
50+
params: { id: '42' },
51+
query: { tab: 'settings' },
52+
hash: '',
53+
matched: ['AppLayout', 'UserProfile'],
54+
});
55+
});
56+
57+
test('returns null name when route name is not a string or symbol', () => {
58+
const router = createMockRouter({
59+
name: undefined,
60+
path: '/',
61+
fullPath: '/',
62+
params: {},
63+
query: {},
64+
hash: '',
65+
matched: [],
66+
});
67+
68+
expect(getRouteContext(router)!.name).toBeNull();
69+
});
70+
71+
test('converts symbol route name to string', () => {
72+
const sym = Symbol('my-route');
73+
const router = createMockRouter({
74+
name: sym,
75+
path: '/',
76+
fullPath: '/',
77+
params: {},
78+
query: {},
79+
hash: '',
80+
matched: [],
81+
});
82+
83+
expect(getRouteContext(router)!.name).toBe(sym.toString());
84+
});
85+
86+
test('converts symbol matched record names to string', () => {
87+
const sym = Symbol('layout');
88+
const router = createMockRouter({
89+
name: 'home',
90+
path: '/',
91+
fullPath: '/',
92+
params: {},
93+
query: {},
94+
hash: '',
95+
matched: [{ name: sym }, { name: 'Home' }],
96+
});
97+
98+
expect(getRouteContext(router)!.matched).toEqual([sym.toString(), 'Home']);
99+
});
100+
101+
test('uses "unknown" for matched records without a name', () => {
102+
const router = createMockRouter({
103+
name: 'home',
104+
path: '/',
105+
fullPath: '/',
106+
params: {},
107+
query: {},
108+
hash: '',
109+
matched: [{ name: undefined }, {}],
110+
});
111+
112+
expect(getRouteContext(router)!.matched).toEqual(['unknown', 'unknown']);
113+
});
114+
115+
test('defaults to empty values when route properties are missing', () => {
116+
const router = createMockRouter({});
117+
118+
expect(getRouteContext(router)).toEqual({
119+
name: null,
120+
path: '',
121+
fullPath: '',
122+
params: {},
123+
query: {},
124+
hash: '',
125+
matched: [],
126+
});
127+
});
128+
129+
test('handles route with hash and query params', () => {
130+
const router = createMockRouter({
131+
name: 'docs',
132+
path: '/docs',
133+
fullPath: '/docs?version=3#api',
134+
params: {},
135+
query: { version: '3' },
136+
hash: '#api',
137+
matched: [{ name: 'Docs' }],
138+
});
139+
140+
const result = getRouteContext(router)!;
141+
expect(result.hash).toBe('#api');
142+
expect(result.query).toEqual({ version: '3' });
143+
expect(result.fullPath).toBe('/docs?version=3#api');
144+
});
145+
});

0 commit comments

Comments
 (0)