Skip to content

Commit 4f87f32

Browse files
novemberbornsindresorhus
authored andcommitted
Format values when AssertionErrors are created
Format statements, generate diffs and otherwise format values when AssertionErrors are created. Make labels contextual to the assertion. Always print power-assert statements in mini and verbose reporters, but after the values. Accept the duplication when the statement covers the value passed to the assertion. Detect whether `t.throws()` failed because no error was thrown, or because the thrown error did not meet expectations. Explicitly test `t.fail()` default message in assertion test.
1 parent e5108a2 commit 4f87f32

File tree

18 files changed

+845
-704
lines changed

18 files changed

+845
-704
lines changed

lib/assert.js

Lines changed: 72 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@ const isObservable = require('is-observable');
77
const isPromise = require('is-promise');
88
const jestSnapshot = require('jest-snapshot');
99
const enhanceAssert = require('./enhance-assert');
10+
const formatAssertError = require('./format-assert-error');
1011
const snapshotState = require('./snapshot-state');
1112

1213
class AssertionError extends Error {
1314
constructor(opts) {
1415
super(opts.message || '');
1516
this.name = 'AssertionError';
1617

17-
this.actual = opts.actual;
1818
this.assertion = opts.assertion;
19-
this.expected = opts.expected;
20-
this.hasActual = 'actual' in opts;
21-
this.hasExpected = 'expected' in opts;
2219
this.operator = opts.operator;
20+
this.values = opts.values || [];
2321

2422
// Reserved for power-assert statements
25-
this.statements = null;
23+
this.statements = [];
2624

2725
if (opts.stack) {
2826
this.stack = opts.stack;
@@ -58,24 +56,28 @@ function wrapAssertions(callbacks) {
5856
if (actual === expected) {
5957
pass(this);
6058
} else {
59+
const diff = formatAssertError.formatDiff(actual, expected);
60+
const values = diff ? [diff] : [
61+
formatAssertError.formatWithLabel('Actual:', actual),
62+
formatAssertError.formatWithLabel('Must be strictly equal to:', expected)
63+
];
64+
6165
fail(this, new AssertionError({
62-
actual,
6366
assertion: 'is',
64-
expected,
6567
message,
66-
operator: '==='
68+
operator: '===',
69+
values
6770
}));
6871
}
6972
},
7073

7174
not(actual, expected, message) {
7275
if (actual === expected) {
7376
fail(this, new AssertionError({
74-
actual,
7577
assertion: 'not',
76-
expected,
7778
message,
78-
operator: '!=='
79+
operator: '!==',
80+
values: [formatAssertError.formatWithLabel('Value is strictly equal:', actual)]
7981
}));
8082
} else {
8183
pass(this);
@@ -86,22 +88,26 @@ function wrapAssertions(callbacks) {
8688
if (deepEqual(actual, expected)) {
8789
pass(this);
8890
} else {
91+
const diff = formatAssertError.formatDiff(actual, expected);
92+
const values = diff ? [diff] : [
93+
formatAssertError.formatWithLabel('Actual:', actual),
94+
formatAssertError.formatWithLabel('Must be deeply equal to:', expected)
95+
];
96+
8997
fail(this, new AssertionError({
90-
actual,
9198
assertion: 'deepEqual',
92-
expected,
93-
message
99+
message,
100+
values
94101
}));
95102
}
96103
},
97104

98105
notDeepEqual(actual, expected, message) {
99106
if (deepEqual(actual, expected)) {
100107
fail(this, new AssertionError({
101-
actual,
102108
assertion: 'notDeepEqual',
103-
expected,
104-
message
109+
message,
110+
values: [formatAssertError.formatWithLabel('Value is deeply equal:', actual)]
105111
}));
106112
} else {
107113
pass(this);
@@ -116,8 +122,9 @@ function wrapAssertions(callbacks) {
116122
promise = observableToPromise(fn);
117123
} else if (typeof fn !== 'function') {
118124
fail(this, new AssertionError({
119-
actual: fn,
120-
message: '`t.throws()` must be called with a function, Promise, or Observable'
125+
assertion: 'throws',
126+
message: '`t.throws()` must be called with a function, Promise, or Observable',
127+
values: [formatAssertError.formatWithLabel('Called with:', fn)]
121128
}));
122129
return;
123130
}
@@ -132,21 +139,28 @@ function wrapAssertions(callbacks) {
132139
}
133140

134141
const test = fn => {
142+
let actual;
143+
let threw = false;
135144
try {
136-
let retval;
137145
coreAssert.throws(() => {
138146
try {
139147
fn();
140148
} catch (err) {
141-
retval = err;
149+
actual = err;
150+
threw = true;
142151
throw err;
143152
}
144153
}, coreAssertThrowsErrorArg);
145-
return retval;
154+
return actual;
146155
} catch (err) {
156+
const values = threw ?
157+
[formatAssertError.formatWithLabel('Threw unexpected exception:', actual)] :
158+
null;
159+
147160
throw new AssertionError({
148161
assertion: 'throws',
149-
message
162+
message,
163+
values
150164
});
151165
}
152166
};
@@ -174,8 +188,9 @@ function wrapAssertions(callbacks) {
174188
promise = observableToPromise(fn);
175189
} else if (typeof fn !== 'function') {
176190
fail(this, new AssertionError({
177-
actual: fn,
178-
message: '`t.notThrows()` must be called with a function, Promise, or Observable'
191+
assertion: 'notThrows',
192+
message: '`t.notThrows()` must be called with a function, Promise, or Observable',
193+
values: [formatAssertError.formatWithLabel('Called with:', fn)]
179194
}));
180195
return;
181196
}
@@ -185,9 +200,9 @@ function wrapAssertions(callbacks) {
185200
coreAssert.doesNotThrow(fn);
186201
} catch (err) {
187202
throw new AssertionError({
188-
actual: err.actual,
189203
assertion: 'notThrows',
190-
message
204+
message,
205+
values: [formatAssertError.formatWithLabel('Threw:', err.actual)]
191206
});
192207
}
193208
};
@@ -212,9 +227,9 @@ function wrapAssertions(callbacks) {
212227
ifError(actual, message) {
213228
if (actual) {
214229
fail(this, new AssertionError({
215-
actual,
216230
assertion: 'ifError',
217-
message
231+
message,
232+
values: [formatAssertError.formatWithLabel('Error:', actual)]
218233
}));
219234
} else {
220235
pass(this);
@@ -226,11 +241,16 @@ function wrapAssertions(callbacks) {
226241
if (result.pass) {
227242
pass(this);
228243
} else {
244+
const diff = formatAssertError.formatDiff(actual, result.expected);
245+
const values = diff ? [diff] : [
246+
formatAssertError.formatWithLabel('Actual:', actual),
247+
formatAssertError.formatWithLabel('Must be deeply equal to:', result.expected)
248+
];
249+
229250
fail(this, new AssertionError({
230-
actual,
231251
assertion: 'snapshot',
232-
expected: result.expected,
233-
message: result.message
252+
message: result.message,
253+
values
234254
}));
235255
}
236256
}
@@ -240,69 +260,67 @@ function wrapAssertions(callbacks) {
240260
truthy(actual, message) {
241261
if (!actual) {
242262
throw new AssertionError({
243-
actual,
244263
assertion: 'truthy',
245-
expected: true,
246264
message,
247-
operator: '=='
265+
operator: '!!',
266+
values: [formatAssertError.formatWithLabel('Value is not truthy:', actual)]
248267
});
249268
}
250269
},
251270

252271
falsy(actual, message) {
253272
if (actual) {
254273
throw new AssertionError({
255-
actual,
256274
assertion: 'falsy',
257-
expected: false,
258275
message,
259-
operator: '=='
276+
operator: '!',
277+
values: [formatAssertError.formatWithLabel('Value is not falsy:', actual)]
260278
});
261279
}
262280
},
263281

264282
true(actual, message) {
265283
if (actual !== true) {
266284
throw new AssertionError({
267-
actual,
268285
assertion: 'true',
269-
expected: true,
270286
message,
271-
operator: '==='
287+
values: [formatAssertError.formatWithLabel('Value is not `true`:', actual)]
272288
});
273289
}
274290
},
275291

276292
false(actual, message) {
277293
if (actual !== false) {
278294
throw new AssertionError({
279-
actual,
280295
assertion: 'false',
281-
expected: false,
282296
message,
283-
operator: '==='
297+
values: [formatAssertError.formatWithLabel('Value is not `false`:', actual)]
284298
});
285299
}
286300
},
287301

288-
regex(actual, expected, message) {
289-
if (!expected.test(actual)) {
302+
regex(string, regex, message) {
303+
if (!regex.test(string)) {
290304
throw new AssertionError({
291-
actual,
292305
assertion: 'regex',
293-
expected,
294-
message
306+
message,
307+
values: [
308+
formatAssertError.formatWithLabel('Value must match expression:', string),
309+
formatAssertError.formatWithLabel('Regular expression:', regex)
310+
]
295311
});
296312
}
297313
},
298314

299-
notRegex(actual, expected, message) {
300-
if (expected.test(actual)) {
315+
notRegex(string, regex, message) {
316+
if (regex.test(string)) {
301317
throw new AssertionError({
302-
actual,
303318
assertion: 'notRegex',
304-
expected,
305-
message
319+
message,
320+
values: [
321+
formatAssertError.formatWithLabel('Value must not match expression:', string),
322+
formatAssertError.formatWithLabel('Regular expression:', regex)
323+
]
306324
});
307325
}
308326
}

lib/enhance-assert.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22
const dotProp = require('dot-prop');
3+
const formatValue = require('./format-assert-error');
34

45
// When adding patterns, don't forget to add to
56
// https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json
@@ -36,7 +37,7 @@ const formatter = context => {
3637
return args
3738
.map(arg => {
3839
const range = getNode(ast, arg.espath).range;
39-
return [computeStatement(tokens, range), arg.value];
40+
return [computeStatement(tokens, range), formatValue(arg.value)];
4041
})
4142
.reverse();
4243
};
@@ -47,7 +48,9 @@ const enhanceAssert = (pass, fail, assertions) => {
4748
destructive: true,
4849
onError(event) {
4950
const error = event.error;
50-
error.statements = formatter(event.powerAssertContext);
51+
if (event.powerAssertContext) { // Context may be missing in internal tests.
52+
error.statements = formatter(event.powerAssertContext);
53+
}
5154
fail(this, error);
5255
},
5356
onSuccess() {

0 commit comments

Comments
 (0)