Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 93 additions & 62 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,23 @@ 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;
} else {
Error.captureStackTrace(this, opts.stackStartFunction);
}
}
}
Expand All @@ -52,35 +48,36 @@ function wrapAssertions(callbacks) {
fail(message) {
fail(this, new AssertionError({
assertion: 'fail',
message: message || 'Test failed via t.fail()',
stackStartFunction: assertions.fail
message: message || 'Test failed via `t.fail()`'
}));
},

is(actual, expected, message) {
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: '===',
stackStartFunction: assertions.is
values
}));
}
},

not(actual, expected, message) {
if (actual === expected) {
fail(this, new AssertionError({
actual,
assertion: 'not',
expected,
message,
operator: '!==',
stackStartFunction: assertions.not
values: [formatAssertError.formatWithLabel('Value is strictly equal:', actual)]
}));
} else {
pass(this);
Expand All @@ -91,24 +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,
stackStartFunction: assertions.deepEqual
values
}));
}
},

notDeepEqual(actual, expected, message) {
if (deepEqual(actual, expected)) {
fail(this, new AssertionError({
actual,
assertion: 'notDeepEqual',
expected,
message,
stackStartFunction: assertions.notDeepEqual
values: [formatAssertError.formatWithLabel('Value is deeply equal:', actual)]
}));
} else {
pass(this);
Expand All @@ -123,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 @@ -139,22 +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,
stackStartFunction: assertions.throws
values
});
}
};
Expand Down Expand Up @@ -182,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 @@ -193,10 +200,9 @@ function wrapAssertions(callbacks) {
coreAssert.doesNotThrow(fn);
} catch (err) {
throw new AssertionError({
actual: err.actual,
assertion: 'notThrows',
message,
stackStartFunction: assertions.notThrows
values: [formatAssertError.formatWithLabel('Threw:', err.actual)]
});
}
};
Expand All @@ -221,10 +227,9 @@ function wrapAssertions(callbacks) {
ifError(actual, message) {
if (actual) {
fail(this, new AssertionError({
actual,
assertion: 'ifError',
message,
stackStartFunction: assertions.ifError
values: [formatAssertError.formatWithLabel('Error:', actual)]
}));
} else {
pass(this);
Expand All @@ -236,12 +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,
stackStartFunction: assertions.snapshot
values
}));
}
}
Expand All @@ -251,75 +260,97 @@ function wrapAssertions(callbacks) {
truthy(actual, message) {
if (!actual) {
throw new AssertionError({
actual,
assertion: 'truthy',
expected: true,
message,
operator: '==',
stackStartFunction: enhancedAssertions.truthy
operator: '!!',
values: [formatAssertError.formatWithLabel('Value is not truthy:', actual)]
});
}
},

falsy(actual, message) {
if (actual) {
throw new AssertionError({
actual,
assertion: 'falsy',
expected: false,
message,
operator: '==',
stackStartFunction: enhancedAssertions.falsy
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: '===',
stackStartFunction: enhancedAssertions.true
values: [formatAssertError.formatWithLabel('Value is not `true`:', actual)]
});
}
},

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

regex(actual, expected, message) {
if (!expected.test(actual)) {
regex(string, regex, message) {
if (typeof string !== 'string') {
throw new AssertionError({
assertion: 'regex',
message: '`t.regex()` must be called with a string',
values: [formatAssertError.formatWithLabel('Called with:', string)]
});
}
if (!(regex instanceof RegExp)) {
throw new AssertionError({
assertion: 'regex',
message: '`t.regex()` must be called with a regular expression',
values: [formatAssertError.formatWithLabel('Called with:', regex)]
});
}

if (!regex.test(string)) {
throw new AssertionError({
actual,
assertion: 'regex',
expected,
message,
stackStartFunction: enhancedAssertions.regex
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 (typeof string !== 'string') {
throw new AssertionError({
assertion: 'notRegex',
message: '`t.notRegex()` must be called with a string',
values: [formatAssertError.formatWithLabel('Called with:', string)]
});
}
if (!(regex instanceof RegExp)) {
throw new AssertionError({
assertion: 'notRegex',
message: '`t.notRegex()` must be called with a regular expression',
values: [formatAssertError.formatWithLabel('Called with:', regex)]
});
}

if (regex.test(string)) {
throw new AssertionError({
actual,
assertion: 'notRegex',
expected,
message,
stackStartFunction: enhancedAssertions.notRegex
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, {maxDepth: 1})];
})
.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