Skip to content

Commit

Permalink
Format values when AssertionErrors are created
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
novemberborn authored and sindresorhus committed Mar 17, 2017
1 parent e5108a2 commit 4f87f32
Show file tree
Hide file tree
Showing 18 changed files with 845 additions and 704 deletions.
126 changes: 72 additions & 54 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ const isObservable = require('is-observable');
const isPromise = require('is-promise');
const jestSnapshot = require('jest-snapshot');
const enhanceAssert = require('./enhance-assert');
const formatAssertError = require('./format-assert-error');
const snapshotState = require('./snapshot-state');

class AssertionError extends Error {
constructor(opts) {
super(opts.message || '');
this.name = 'AssertionError';

this.actual = opts.actual;
this.assertion = opts.assertion;
this.expected = opts.expected;
this.hasActual = 'actual' in opts;
this.hasExpected = 'expected' in opts;
this.operator = opts.operator;
this.values = opts.values || [];

// Reserved for power-assert statements
this.statements = null;
this.statements = [];

if (opts.stack) {
this.stack = opts.stack;
Expand Down Expand Up @@ -58,24 +56,28 @@ function wrapAssertions(callbacks) {
if (actual === expected) {
pass(this);
} else {
const diff = formatAssertError.formatDiff(actual, expected);
const values = diff ? [diff] : [
formatAssertError.formatWithLabel('Actual:', actual),
formatAssertError.formatWithLabel('Must be strictly equal to:', expected)
];

fail(this, new AssertionError({
actual,
assertion: 'is',
expected,
message,
operator: '==='
operator: '===',
values
}));
}
},

not(actual, expected, message) {
if (actual === expected) {
fail(this, new AssertionError({
actual,
assertion: 'not',
expected,
message,
operator: '!=='
operator: '!==',
values: [formatAssertError.formatWithLabel('Value is strictly equal:', actual)]
}));
} else {
pass(this);
Expand All @@ -86,22 +88,26 @@ function wrapAssertions(callbacks) {
if (deepEqual(actual, expected)) {
pass(this);
} else {
const diff = formatAssertError.formatDiff(actual, expected);
const values = diff ? [diff] : [
formatAssertError.formatWithLabel('Actual:', actual),
formatAssertError.formatWithLabel('Must be deeply equal to:', expected)
];

fail(this, new AssertionError({
actual,
assertion: 'deepEqual',
expected,
message
message,
values
}));
}
},

notDeepEqual(actual, expected, message) {
if (deepEqual(actual, expected)) {
fail(this, new AssertionError({
actual,
assertion: 'notDeepEqual',
expected,
message
message,
values: [formatAssertError.formatWithLabel('Value is deeply equal:', actual)]
}));
} else {
pass(this);
Expand All @@ -116,8 +122,9 @@ function wrapAssertions(callbacks) {
promise = observableToPromise(fn);
} else if (typeof fn !== 'function') {
fail(this, new AssertionError({
actual: fn,
message: '`t.throws()` must be called with a function, Promise, or Observable'
assertion: 'throws',
message: '`t.throws()` must be called with a function, Promise, or Observable',
values: [formatAssertError.formatWithLabel('Called with:', fn)]
}));
return;
}
Expand All @@ -132,21 +139,28 @@ function wrapAssertions(callbacks) {
}

const test = fn => {
let actual;
let threw = false;
try {
let retval;
coreAssert.throws(() => {
try {
fn();
} catch (err) {
retval = err;
actual = err;
threw = true;
throw err;
}
}, coreAssertThrowsErrorArg);
return retval;
return actual;
} catch (err) {
const values = threw ?
[formatAssertError.formatWithLabel('Threw unexpected exception:', actual)] :
null;

throw new AssertionError({
assertion: 'throws',
message
message,
values
});
}
};
Expand Down Expand Up @@ -174,8 +188,9 @@ function wrapAssertions(callbacks) {
promise = observableToPromise(fn);
} else if (typeof fn !== 'function') {
fail(this, new AssertionError({
actual: fn,
message: '`t.notThrows()` must be called with a function, Promise, or Observable'
assertion: 'notThrows',
message: '`t.notThrows()` must be called with a function, Promise, or Observable',
values: [formatAssertError.formatWithLabel('Called with:', fn)]
}));
return;
}
Expand All @@ -185,9 +200,9 @@ function wrapAssertions(callbacks) {
coreAssert.doesNotThrow(fn);
} catch (err) {
throw new AssertionError({
actual: err.actual,
assertion: 'notThrows',
message
message,
values: [formatAssertError.formatWithLabel('Threw:', err.actual)]
});
}
};
Expand All @@ -212,9 +227,9 @@ function wrapAssertions(callbacks) {
ifError(actual, message) {
if (actual) {
fail(this, new AssertionError({
actual,
assertion: 'ifError',
message
message,
values: [formatAssertError.formatWithLabel('Error:', actual)]
}));
} else {
pass(this);
Expand All @@ -226,11 +241,16 @@ function wrapAssertions(callbacks) {
if (result.pass) {
pass(this);
} else {
const diff = formatAssertError.formatDiff(actual, result.expected);
const values = diff ? [diff] : [
formatAssertError.formatWithLabel('Actual:', actual),
formatAssertError.formatWithLabel('Must be deeply equal to:', result.expected)
];

fail(this, new AssertionError({
actual,
assertion: 'snapshot',
expected: result.expected,
message: result.message
message: result.message,
values
}));
}
}
Expand All @@ -240,69 +260,67 @@ function wrapAssertions(callbacks) {
truthy(actual, message) {
if (!actual) {
throw new AssertionError({
actual,
assertion: 'truthy',
expected: true,
message,
operator: '=='
operator: '!!',
values: [formatAssertError.formatWithLabel('Value is not truthy:', actual)]
});
}
},

falsy(actual, message) {
if (actual) {
throw new AssertionError({
actual,
assertion: 'falsy',
expected: false,
message,
operator: '=='
operator: '!',
values: [formatAssertError.formatWithLabel('Value is not falsy:', actual)]
});
}
},

true(actual, message) {
if (actual !== true) {
throw new AssertionError({
actual,
assertion: 'true',
expected: true,
message,
operator: '==='
values: [formatAssertError.formatWithLabel('Value is not `true`:', actual)]
});
}
},

false(actual, message) {
if (actual !== false) {
throw new AssertionError({
actual,
assertion: 'false',
expected: false,
message,
operator: '==='
values: [formatAssertError.formatWithLabel('Value is not `false`:', actual)]
});
}
},

regex(actual, expected, message) {
if (!expected.test(actual)) {
regex(string, regex, message) {
if (!regex.test(string)) {
throw new AssertionError({
actual,
assertion: 'regex',
expected,
message
message,
values: [
formatAssertError.formatWithLabel('Value must match expression:', string),
formatAssertError.formatWithLabel('Regular expression:', regex)
]
});
}
},

notRegex(actual, expected, message) {
if (expected.test(actual)) {
notRegex(string, regex, message) {
if (regex.test(string)) {
throw new AssertionError({
actual,
assertion: 'notRegex',
expected,
message
message,
values: [
formatAssertError.formatWithLabel('Value must not match expression:', string),
formatAssertError.formatWithLabel('Regular expression:', regex)
]
});
}
}
Expand Down
7 changes: 5 additions & 2 deletions lib/enhance-assert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const dotProp = require('dot-prop');
const formatValue = require('./format-assert-error');

// When adding patterns, don't forget to add to
// https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json
Expand Down Expand Up @@ -36,7 +37,7 @@ const formatter = context => {
return args
.map(arg => {
const range = getNode(ast, arg.espath).range;
return [computeStatement(tokens, range), arg.value];
return [computeStatement(tokens, range), formatValue(arg.value)];
})
.reverse();
};
Expand All @@ -47,7 +48,9 @@ const enhanceAssert = (pass, fail, assertions) => {
destructive: true,
onError(event) {
const error = event.error;
error.statements = formatter(event.powerAssertContext);
if (event.powerAssertContext) { // Context may be missing in internal tests.
error.statements = formatter(event.powerAssertContext);
}
fail(this, error);
},
onSuccess() {
Expand Down
Loading

0 comments on commit 4f87f32

Please sign in to comment.