Skip to content

Commit 61f9a2b

Browse files
author
Sébastien Henau
committed
tests: add vue tests for the new callback hooks
1 parent 7eaffcd commit 61f9a2b

File tree

1 file changed

+291
-1
lines changed

1 file changed

+291
-1
lines changed

packages/vue/tests/FlareErrorBoundary.test.ts

Lines changed: 291 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
33
import { defineComponent, h, nextTick } from 'vue';
44

55
import { FlareErrorBoundary } from '../src/FlareErrorBoundary';
6+
import { FlareVueContext } from '../src/types';
67

78
const mockReport = vi.fn();
89

@@ -29,7 +30,7 @@ describe('FlareErrorBoundary', () => {
2930

3031
beforeEach(() => {
3132
testError = new Error('test error');
32-
mockReport.mockClear();
33+
mockReport.mockReset();
3334
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
3435
});
3536

@@ -293,4 +294,293 @@ describe('FlareErrorBoundary', () => {
293294
});
294295
}).toThrow('report failed');
295296
});
297+
298+
test('calls beforeEvaluate before reporting', async () => {
299+
const callOrder: string[] = [];
300+
301+
const beforeEvaluate = vi.fn(() => callOrder.push('beforeEvaluate'));
302+
mockReport.mockImplementationOnce(() => callOrder.push('report'));
303+
304+
mount(FlareErrorBoundary, {
305+
props: { beforeEvaluate },
306+
slots: {
307+
default: () => h(ThrowingComponent),
308+
fallback: () => h('div', 'Error'),
309+
},
310+
});
311+
312+
await nextTick();
313+
314+
expect(beforeEvaluate).toHaveBeenCalledOnce();
315+
expect(callOrder).toEqual(['beforeEvaluate', 'report']);
316+
});
317+
318+
test('calls beforeEvaluate with error, instance, and info', async () => {
319+
const beforeEvaluate = vi.fn();
320+
321+
mount(FlareErrorBoundary, {
322+
props: { beforeEvaluate },
323+
slots: {
324+
default: () => h(ThrowingComponent),
325+
fallback: () => h('div', 'Error'),
326+
},
327+
});
328+
329+
await nextTick();
330+
331+
expect(beforeEvaluate.mock.calls[0][0].error).toBe(testError);
332+
expect(beforeEvaluate.mock.calls[0][0].instance).toBeDefined();
333+
expect(beforeEvaluate.mock.calls[0][0].info).toEqual(expect.any(String));
334+
});
335+
336+
test('calls beforeSubmit after beforeEvaluate and before reporting', async () => {
337+
const callOrder: string[] = [];
338+
339+
const beforeEvaluate = vi.fn(() => callOrder.push('beforeEvaluate'));
340+
const beforeSubmit = vi.fn((params: { context: FlareVueContext }) => {
341+
callOrder.push('beforeSubmit');
342+
return params.context;
343+
});
344+
mockReport.mockImplementationOnce(() => callOrder.push('report'));
345+
346+
mount(FlareErrorBoundary, {
347+
props: { beforeEvaluate, beforeSubmit },
348+
slots: {
349+
default: () => h(ThrowingComponent),
350+
fallback: () => h('div', 'Error'),
351+
},
352+
});
353+
354+
await nextTick();
355+
356+
expect(beforeSubmit).toHaveBeenCalledOnce();
357+
expect(callOrder).toEqual(['beforeEvaluate', 'beforeSubmit', 'report']);
358+
});
359+
360+
test('calls beforeSubmit with error, instance, info, and context', async () => {
361+
const beforeSubmit = vi.fn(
362+
(params: { error: Error; instance: unknown; info: string; context: FlareVueContext }) => params.context
363+
);
364+
365+
mount(FlareErrorBoundary, {
366+
props: { beforeSubmit },
367+
slots: {
368+
default: () => h(ThrowingComponent),
369+
fallback: () => h('div', 'Error'),
370+
},
371+
});
372+
373+
await nextTick();
374+
375+
expect(beforeSubmit.mock.calls[0][0].error).toBe(testError);
376+
expect(beforeSubmit.mock.calls[0][0].instance).toBeDefined();
377+
expect(beforeSubmit.mock.calls[0][0].info).toEqual(expect.any(String));
378+
expect(beforeSubmit.mock.calls[0][0].context.vue.componentHierarchy).toBeInstanceOf(Array);
379+
expect(beforeSubmit.mock.calls[0][0].context.vue.componentName).toBe('ThrowingComponent');
380+
});
381+
382+
test('beforeSubmit can modify the context before reporting', async () => {
383+
const customHierarchy = ['Custom', 'Modified'];
384+
const beforeSubmit = vi.fn(({ context }: { context: FlareVueContext }) => ({
385+
...context,
386+
vue: {
387+
...context.vue,
388+
componentHierarchy: customHierarchy,
389+
},
390+
}));
391+
392+
mount(FlareErrorBoundary, {
393+
props: { beforeSubmit },
394+
slots: {
395+
default: () => h(ThrowingComponent),
396+
fallback: () => h('div', 'Error'),
397+
},
398+
});
399+
400+
await nextTick();
401+
402+
const reportedContext = mockReport.mock.calls[0][1];
403+
expect(reportedContext.vue.componentHierarchy).toBe(customHierarchy);
404+
});
405+
406+
test('beforeSubmit modified context is passed to afterSubmit', async () => {
407+
const customHierarchy = ['Custom', 'Modified'];
408+
const beforeSubmit = vi.fn(({ context }: { context: FlareVueContext }) => ({
409+
...context,
410+
vue: {
411+
...context.vue,
412+
componentHierarchy: customHierarchy,
413+
},
414+
}));
415+
const afterSubmit = vi.fn();
416+
417+
mount(FlareErrorBoundary, {
418+
props: { beforeSubmit, afterSubmit },
419+
slots: {
420+
default: () => h(ThrowingComponent),
421+
fallback: () => h('div', 'Error'),
422+
},
423+
});
424+
425+
await nextTick();
426+
427+
expect(afterSubmit.mock.calls[0][0].context.vue.componentHierarchy).toBe(customHierarchy);
428+
});
429+
430+
test('calls afterSubmit after reporting', async () => {
431+
const callOrder: string[] = [];
432+
433+
mockReport.mockImplementationOnce(() => callOrder.push('report'));
434+
const afterSubmit = vi.fn(() => {
435+
callOrder.push('afterSubmit');
436+
});
437+
438+
mount(FlareErrorBoundary, {
439+
props: { afterSubmit },
440+
slots: {
441+
default: () => h(ThrowingComponent),
442+
fallback: () => h('div', 'Error'),
443+
},
444+
});
445+
446+
await nextTick();
447+
448+
expect(afterSubmit).toHaveBeenCalledOnce();
449+
expect(afterSubmit.mock.calls[0][0].error).toBe(testError);
450+
expect(afterSubmit.mock.calls[0][0].instance).toBeDefined();
451+
expect(afterSubmit.mock.calls[0][0].info).toEqual(expect.any(String));
452+
expect(afterSubmit.mock.calls[0][0].context.vue.componentHierarchy).toBeInstanceOf(Array);
453+
expect(callOrder).toEqual(['report', 'afterSubmit']);
454+
});
455+
456+
test('uses original context when beforeSubmit does not return', async () => {
457+
const beforeSubmit = vi.fn(() => {
458+
// user forgot to return context
459+
});
460+
461+
mount(FlareErrorBoundary, {
462+
// @ts-expect-error - intentionally testing a user mistake where beforeSubmit does not return
463+
props: { beforeSubmit },
464+
slots: {
465+
default: () => h(ThrowingComponent),
466+
fallback: () => h('div', 'Error'),
467+
},
468+
});
469+
470+
await nextTick();
471+
472+
expect(beforeSubmit).toHaveBeenCalledOnce();
473+
const reportedContext = mockReport.mock.calls[0][1];
474+
expect(reportedContext.vue.componentName).toBe('ThrowingComponent');
475+
expect(reportedContext.vue.componentHierarchy).toBeInstanceOf(Array);
476+
});
477+
478+
test('beforeSubmit modified componentHierarchy is reflected in the fallback render', async () => {
479+
const customHierarchy = ['Custom', 'Modified'];
480+
const beforeSubmit = vi.fn(({ context }: { context: FlareVueContext }) => ({
481+
...context,
482+
vue: {
483+
...context.vue,
484+
componentHierarchy: customHierarchy,
485+
},
486+
}));
487+
488+
const wrapper = mount(FlareErrorBoundary, {
489+
props: { beforeSubmit },
490+
slots: {
491+
default: () => h(ThrowingComponent),
492+
fallback: (props: { componentHierarchy: string[] }) =>
493+
h('span', { class: 'hierarchy' }, props.componentHierarchy.join(',')),
494+
},
495+
});
496+
497+
await nextTick();
498+
499+
expect(wrapper.find('.hierarchy').text()).toBe('Custom,Modified');
500+
});
501+
502+
test('beforeEvaluate throwing prevents reporting and propagates', () => {
503+
const beforeEvaluate = vi.fn(() => {
504+
throw new Error('beforeEvaluate error');
505+
});
506+
507+
expect(() => {
508+
mount(FlareErrorBoundary, {
509+
props: { beforeEvaluate },
510+
slots: {
511+
default: () => h(ThrowingComponent),
512+
fallback: () => h('div', 'Error'),
513+
},
514+
});
515+
}).toThrow('beforeEvaluate error');
516+
517+
expect(beforeEvaluate).toHaveBeenCalledOnce();
518+
expect(mockReport).not.toHaveBeenCalled();
519+
});
520+
521+
test('beforeSubmit throwing prevents reporting and propagates', () => {
522+
const beforeSubmit = vi.fn(() => {
523+
throw new Error('beforeSubmit error');
524+
});
525+
526+
expect(() => {
527+
mount(FlareErrorBoundary, {
528+
props: { beforeSubmit },
529+
slots: {
530+
default: () => h(ThrowingComponent),
531+
fallback: () => h('div', 'Error'),
532+
},
533+
});
534+
}).toThrow('beforeSubmit error');
535+
536+
expect(beforeSubmit).toHaveBeenCalledOnce();
537+
expect(mockReport).not.toHaveBeenCalled();
538+
});
539+
540+
test('afterSubmit throwing propagates after reporting', () => {
541+
const afterSubmit = vi.fn(() => {
542+
throw new Error('afterSubmit error');
543+
});
544+
545+
expect(() => {
546+
mount(FlareErrorBoundary, {
547+
props: { afterSubmit },
548+
slots: {
549+
default: () => h(ThrowingComponent),
550+
fallback: () => h('div', 'Error'),
551+
},
552+
});
553+
}).toThrow('afterSubmit error');
554+
555+
expect(afterSubmit).toHaveBeenCalledOnce();
556+
expect(mockReport).toHaveBeenCalledOnce();
557+
});
558+
559+
test('callbacks fire again after reset and re-throw', async () => {
560+
const beforeEvaluate = vi.fn();
561+
const beforeSubmit = vi.fn((params: { context: FlareVueContext }) => params.context);
562+
const afterSubmit = vi.fn();
563+
564+
const wrapper = mount(FlareErrorBoundary, {
565+
props: { beforeEvaluate, beforeSubmit, afterSubmit },
566+
slots: {
567+
default: () => h(ThrowingComponent),
568+
fallback: (props: { resetErrorBoundary: () => void }) =>
569+
h('button', { onClick: props.resetErrorBoundary }, 'Reset'),
570+
},
571+
});
572+
573+
await nextTick();
574+
575+
expect(beforeEvaluate).toHaveBeenCalledOnce();
576+
expect(beforeSubmit).toHaveBeenCalledOnce();
577+
expect(afterSubmit).toHaveBeenCalledOnce();
578+
579+
await wrapper.find('button').trigger('click');
580+
await nextTick();
581+
582+
expect(beforeEvaluate).toHaveBeenCalledTimes(2);
583+
expect(beforeSubmit).toHaveBeenCalledTimes(2);
584+
expect(afterSubmit).toHaveBeenCalledTimes(2);
585+
});
296586
});

0 commit comments

Comments
 (0)