-
Notifications
You must be signed in to change notification settings - Fork 439
Expand file tree
/
Copy pathconsole.mjs
More file actions
117 lines (105 loc) · 4.37 KB
/
console.mjs
File metadata and controls
117 lines (105 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { spyOn } from '@vitest/spy';
function formatConsoleCall(args) {
// Just calling .join suppresses null/undefined, so we stringify separately
return args.map(String).join(' ');
}
function formatAllCalls(argsList) {
return argsList.map((args) => `"${formatConsoleCall(args)}"`).join(', ');
}
function callAndGetLogs(fn, methodName) {
const spy = spyOn(console, methodName).mockImplementation(() => {});
try {
fn();
return spy.mock.calls;
} finally {
spy.mockRestore();
}
}
function consoleMatcherFactory(chai, utils, methodName, expectInProd) {
return function consoleMatcher(expectedMessages) {
const actual = utils.flag(this, 'object');
if (utils.flag(this, 'negate')) {
// If there's a .not in the assertion chain
const callsArgs = callAndGetLogs(actual, methodName);
if (callsArgs.length === 0) {
return;
}
throw new chai.AssertionError(
`Expect no message but received:\n${formatAllCalls(callsArgs)}`
);
}
if (!Array.isArray(expectedMessages)) {
expectedMessages = [expectedMessages];
}
if (typeof actual !== 'function') {
throw new Error('Expected function to throw error.');
} else if (expectedMessages.some((m) => typeof m !== 'string' && !(m instanceof RegExp))) {
throw new Error(
'Expected a string or a RegExp to compare the thrown error against, or an array of such.'
);
}
const callsArgs = callAndGetLogs(actual, methodName);
if (!expectInProd && process.env.NODE_ENV === 'production') {
if (callsArgs.length !== 0) {
throw new chai.AssertionError(
`Expected console.${
methodName
} to never be called in production mode, but it was called ${
callsArgs.length
} time(s) with ${formatAllCalls(callsArgs)}.`
);
}
} else {
if (callsArgs.length === 0) {
// Result: "string", /regex/
const formattedExpected = expectedMessages
.map((msg) => (typeof msg === 'string' ? JSON.stringify(msg) : msg.toString()))
.join(', ');
throw new chai.AssertionError(
`Expected console.${methodName} to be called with [${
formattedExpected
}], but was never called.`
);
} else {
if (callsArgs.length !== expectedMessages.length) {
throw new chai.AssertionError(
`Expected console.${methodName} to be called ${
expectedMessages.length
} time(s), but was called ${callsArgs.length} time(s).`
);
}
for (let i = 0; i < callsArgs.length; i++) {
const callsArg = callsArgs[i];
const expectedMessage = expectedMessages[i];
const actualMessage = formatConsoleCall(callsArg);
const matches =
typeof expectedMessage === 'string'
? actualMessage === expectedMessage
: expectedMessage.test(actualMessage);
if (!matches) {
throw new chai.AssertionError(
`Expected console.${methodName} to be called with "${
expectedMessage
}", but was called with "${actualMessage}".`
);
}
}
}
}
};
}
/**
* Custom console assertions
* @type {Chai.ChaiPlugin}
*/
export const registerConsoleMatchers = (chai, utils) => {
const customMatchers = {
// FIXME: Add descriptions explaining the what/why of these custom matchers
toLogErrorDev: consoleMatcherFactory(chai, utils, 'error'),
toLogError: consoleMatcherFactory(chai, utils, 'error', true),
toLogWarningDev: consoleMatcherFactory(chai, utils, 'warn'),
};
for (const [name, impl] of Object.entries(customMatchers)) {
utils.addMethod(chai.Assertion.prototype, name, impl);
}
};