|
1 | 1 | // https://github.com/posva/jest-mock-warn/blob/master/src/index.js |
2 | | - |
3 | 2 | import type { MockInstance } from 'vitest' |
4 | 3 | import { afterEach, beforeEach, expect, vi } from 'vitest' |
5 | 4 |
|
6 | 5 | interface CustomMatchers<R = unknown> { |
7 | 6 | toHaveBeenWarned: () => R |
8 | 7 | toHaveBeenWarnedLast: () => R |
9 | 8 | toHaveBeenWarnedTimes: (n: number) => R |
| 9 | + toHaveBeenErrored: () => R |
| 10 | + toHaveBeenErroredLast: () => R |
| 11 | + toHaveBeenErroredTimes: (n: number) => R |
10 | 12 | } |
11 | 13 |
|
12 | 14 | declare module 'vitest' { |
13 | 15 | interface Assertion<T = any> extends CustomMatchers<T> {} |
14 | 16 | interface AsymmetricMatchersContaining extends CustomMatchers {} |
15 | 17 | } |
16 | 18 |
|
17 | | -export function mockWarn() { |
18 | | - let warn: MockInstance<(typeof console)['log']> |
| 19 | +function createMockConsoleMethod(method: 'warn' | 'error') { |
| 20 | + let mockInstance: MockInstance<(typeof console)[typeof method]> |
19 | 21 | const asserted = new Map<string, string | RegExp>() |
20 | 22 |
|
21 | 23 | expect.extend({ |
22 | | - toHaveBeenWarned(received: string | RegExp) { |
| 24 | + [`toHaveBeen${method.charAt(0).toUpperCase() + method.slice(1)}ed`]( |
| 25 | + received: string | RegExp |
| 26 | + ) { |
23 | 27 | asserted.set(received.toString(), received) |
24 | | - const passed = warn.mock.calls.some((args) => |
| 28 | + const passed = mockInstance.mock.calls.some((args) => |
25 | 29 | typeof received === 'string' |
26 | | - ? args[0].includes(received) |
27 | | - : received.test(args[0]) |
| 30 | + ? String(args[0]).includes(received) |
| 31 | + : received.test(String(args[0])) |
28 | 32 | ) |
29 | | - if (passed) { |
30 | | - return { |
31 | | - pass: true, |
32 | | - message: () => `expected "${received}" not to have been warned.`, |
33 | | - } |
34 | | - } else { |
35 | | - const msgs = warn.mock.calls.map((args) => args[0]).join('\n - ') |
36 | | - return { |
37 | | - pass: false, |
38 | | - message: () => |
39 | | - `expected "${received}" to have been warned.\n\nActual messages:\n\n - ${msgs}`, |
40 | | - } |
41 | | - } |
| 33 | + |
| 34 | + return passed |
| 35 | + ? { |
| 36 | + pass: true, |
| 37 | + message: () => |
| 38 | + `expected "${received}" not to have been ${method}ed.`, |
| 39 | + } |
| 40 | + : { |
| 41 | + pass: false, |
| 42 | + message: () => `expected "${received}" to have been ${method}ed.`, |
| 43 | + } |
42 | 44 | }, |
43 | 45 |
|
44 | | - toHaveBeenWarnedLast(received: string | RegExp) { |
| 46 | + [`toHaveBeen${method.charAt(0).toUpperCase() + method.slice(1)}edLast`]( |
| 47 | + received: string | RegExp |
| 48 | + ) { |
45 | 49 | asserted.set(received.toString(), received) |
46 | | - const lastCall = warn.mock.calls[warn.mock.calls.length - 1][0] |
| 50 | + const lastCall = String(mockInstance.mock.calls.at(-1)?.[0]) |
47 | 51 | const passed = |
48 | 52 | typeof received === 'string' |
49 | | - ? lastCall.includes(received) |
| 53 | + ? lastCall?.includes(received) |
50 | 54 | : received.test(lastCall) |
51 | | - if (passed) { |
52 | | - return { |
53 | | - pass: true, |
54 | | - message: () => `expected "${received}" not to have been warned last.`, |
55 | | - } |
56 | | - } else { |
57 | | - const msgs = warn.mock.calls.map((args) => args[0]).join('\n - ') |
58 | | - return { |
59 | | - pass: false, |
60 | | - message: () => |
61 | | - `expected "${received}" to have been warned last.\n\nActual messages:\n\n - ${msgs}`, |
62 | | - } |
63 | | - } |
| 55 | + |
| 56 | + return passed |
| 57 | + ? { |
| 58 | + pass: true, |
| 59 | + message: () => |
| 60 | + `expected "${received}" not to have been ${method}ed last.`, |
| 61 | + } |
| 62 | + : { |
| 63 | + pass: false, |
| 64 | + message: () => |
| 65 | + `expected "${received}" to have been ${method}ed last.`, |
| 66 | + } |
64 | 67 | }, |
65 | 68 |
|
66 | | - toHaveBeenWarnedTimes(received: string | RegExp, n: number) { |
| 69 | + [`toHaveBeen${method.charAt(0).toUpperCase() + method.slice(1)}edTimes`]( |
| 70 | + received: string | RegExp, |
| 71 | + n: number |
| 72 | + ) { |
67 | 73 | asserted.set(received.toString(), received) |
68 | | - let found = 0 |
69 | | - warn.mock.calls.forEach((args) => { |
70 | | - const isFound = |
71 | | - typeof received === 'string' |
72 | | - ? args[0].includes(received) |
73 | | - : received.test(args[0]) |
74 | | - if (isFound) { |
75 | | - found++ |
76 | | - } |
77 | | - }) |
| 74 | + const count = mockInstance.mock.calls.filter((args) => |
| 75 | + typeof received === 'string' |
| 76 | + ? String(args[0]).includes(received) |
| 77 | + : received.test(String(args[0])) |
| 78 | + ).length |
78 | 79 |
|
79 | | - if (found === n) { |
80 | | - return { |
81 | | - pass: true, |
82 | | - message: () => |
83 | | - `expected "${received}" to have been warned ${n} times.`, |
84 | | - } |
85 | | - } else { |
86 | | - return { |
87 | | - pass: false, |
88 | | - message: () => |
89 | | - `expected "${received}" to have been warned ${n} times but got ${found}.`, |
90 | | - } |
91 | | - } |
| 80 | + return count === n |
| 81 | + ? { |
| 82 | + pass: true, |
| 83 | + message: () => |
| 84 | + `expected "${received}" to have been ${method}ed ${n} times.`, |
| 85 | + } |
| 86 | + : { |
| 87 | + pass: false, |
| 88 | + message: () => |
| 89 | + `expected "${received}" to have been ${method}ed ${n} times but got ${count}.`, |
| 90 | + } |
92 | 91 | }, |
93 | 92 | }) |
94 | 93 |
|
95 | 94 | beforeEach(() => { |
96 | 95 | asserted.clear() |
97 | | - warn = vi.spyOn(console, 'warn') |
98 | | - warn.mockImplementation(() => {}) |
| 96 | + mockInstance = vi.spyOn(console, method).mockImplementation(() => {}) |
99 | 97 | }) |
100 | 98 |
|
101 | 99 | afterEach(() => { |
102 | 100 | const assertedArray = Array.from(asserted) |
103 | | - const nonAssertedWarnings = warn.mock.calls |
104 | | - .map((args) => args[0]) |
105 | | - .filter((received) => { |
106 | | - return !assertedArray.some(([_key, assertedMsg]) => { |
107 | | - return typeof assertedMsg === 'string' |
108 | | - ? received.includes(assertedMsg) |
109 | | - : assertedMsg.test(received) |
110 | | - }) |
111 | | - }) |
112 | | - warn.mockRestore() |
113 | | - if (nonAssertedWarnings.length) { |
114 | | - nonAssertedWarnings.forEach((warning) => { |
115 | | - console.warn(warning) |
| 101 | + const unassertedLogs = mockInstance.mock.calls |
| 102 | + .map((args) => String(args[0])) |
| 103 | + .filter( |
| 104 | + (msg) => |
| 105 | + !assertedArray.some(([_key, assertedMsg]) => |
| 106 | + typeof assertedMsg === 'string' |
| 107 | + ? msg.includes(assertedMsg) |
| 108 | + : assertedMsg.test(msg) |
| 109 | + ) |
| 110 | + ) |
| 111 | + |
| 112 | + mockInstance.mockRestore() |
| 113 | + |
| 114 | + if (unassertedLogs.length) { |
| 115 | + unassertedLogs.forEach((msg) => console[method](msg)) |
| 116 | + throw new Error(`Test case threw unexpected ${method}s.`, { |
| 117 | + cause: unassertedLogs, |
116 | 118 | }) |
117 | | - throw new Error(`test case threw unexpected warnings.`) |
118 | 119 | } |
119 | 120 | }) |
120 | 121 | } |
121 | 122 |
|
122 | | -interface CustomMatchers<R = unknown> { |
123 | | - toHaveBeenWarned: () => R |
124 | | - toHaveBeenWarnedLast: () => R |
125 | | - toHaveBeenWarnedTimes: (n: number) => R |
| 123 | +export function mockWarn() { |
| 124 | + createMockConsoleMethod('warn') |
126 | 125 | } |
127 | 126 |
|
128 | | -declare module 'vitest' { |
129 | | - interface Assertion<T = any> extends CustomMatchers<T> {} |
130 | | - interface AsymmetricMatchersContaining extends CustomMatchers {} |
| 127 | +export function mockConsoleError() { |
| 128 | + createMockConsoleMethod('error') |
131 | 129 | } |
0 commit comments