wallet-core/node_modules/ava/lib/assert.js

434 lines
11 KiB
JavaScript
Raw Normal View History

2017-05-28 00:38:50 +02:00
'use strict';
2017-08-14 05:01:11 +02:00
const concordance = require('concordance');
2017-05-28 00:38:50 +02:00
const coreAssert = require('core-assert');
const observableToPromise = require('observable-to-promise');
const isObservable = require('is-observable');
const isPromise = require('is-promise');
2017-08-14 05:01:11 +02:00
const concordanceOptions = require('./concordance-options').default;
const concordanceDiffOptions = require('./concordance-options').diff;
2017-05-28 00:38:50 +02:00
const enhanceAssert = require('./enhance-assert');
2017-08-14 05:01:11 +02:00
const snapshotManager = require('./snapshot-manager');
function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
options = Object.assign({}, options, concordanceDiffOptions);
return {
label: 'Difference:',
formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options)
};
}
function formatDescriptorWithLabel(label, descriptor) {
return {
label,
formatted: concordance.formatDescriptor(descriptor, concordanceOptions)
};
}
function formatWithLabel(label, value) {
return formatDescriptorWithLabel(label, concordance.describe(value, concordanceOptions));
}
2017-05-28 00:38:50 +02:00
class AssertionError extends Error {
constructor(opts) {
super(opts.message || '');
this.name = 'AssertionError';
this.assertion = opts.assertion;
this.fixedSource = opts.fixedSource;
this.improperUsage = opts.improperUsage || false;
this.operator = opts.operator;
this.values = opts.values || [];
2017-08-14 05:01:11 +02:00
// Raw expected and actual objects are stored for custom reporters
// (such as wallaby.js), that manage worker processes directly and
// use the values for custom diff views
this.raw = opts.raw;
2017-05-28 00:38:50 +02:00
// Reserved for power-assert statements
this.statements = [];
if (opts.stack) {
this.stack = opts.stack;
}
}
}
exports.AssertionError = AssertionError;
function getStack() {
const obj = {};
Error.captureStackTrace(obj, getStack);
return obj.stack;
}
function wrapAssertions(callbacks) {
const pass = callbacks.pass;
const pending = callbacks.pending;
const fail = callbacks.fail;
const noop = () => {};
const makeRethrow = reason => () => {
throw reason;
};
const assertions = {
pass() {
pass(this);
},
fail(message) {
fail(this, new AssertionError({
assertion: 'fail',
message: message || 'Test failed via `t.fail()`'
}));
},
is(actual, expected, message) {
2017-08-14 05:01:11 +02:00
if (Object.is(actual, expected)) {
2017-05-28 00:38:50 +02:00
pass(this);
} else {
2017-08-14 05:01:11 +02:00
const actualDescriptor = concordance.describe(actual, concordanceOptions);
const expectedDescriptor = concordance.describe(expected, concordanceOptions);
2017-05-28 00:38:50 +02:00
fail(this, new AssertionError({
assertion: 'is',
message,
2017-08-14 05:01:11 +02:00
raw: {actual, expected},
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
2017-05-28 00:38:50 +02:00
}));
}
},
not(actual, expected, message) {
2017-08-14 05:01:11 +02:00
if (Object.is(actual, expected)) {
2017-05-28 00:38:50 +02:00
fail(this, new AssertionError({
assertion: 'not',
message,
2017-08-14 05:01:11 +02:00
raw: {actual, expected},
values: [formatWithLabel('Value is the same as:', actual)]
2017-05-28 00:38:50 +02:00
}));
} else {
pass(this);
}
},
deepEqual(actual, expected, message) {
2017-08-14 05:01:11 +02:00
const result = concordance.compare(actual, expected, concordanceOptions);
if (result.pass) {
2017-05-28 00:38:50 +02:00
pass(this);
} else {
2017-08-14 05:01:11 +02:00
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
2017-05-28 00:38:50 +02:00
fail(this, new AssertionError({
assertion: 'deepEqual',
message,
2017-08-14 05:01:11 +02:00
raw: {actual, expected},
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
2017-05-28 00:38:50 +02:00
}));
}
},
notDeepEqual(actual, expected, message) {
2017-08-14 05:01:11 +02:00
const result = concordance.compare(actual, expected, concordanceOptions);
if (result.pass) {
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
2017-05-28 00:38:50 +02:00
fail(this, new AssertionError({
assertion: 'notDeepEqual',
message,
2017-08-14 05:01:11 +02:00
raw: {actual, expected},
values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)]
2017-05-28 00:38:50 +02:00
}));
} else {
pass(this);
}
},
throws(fn, err, message) {
let promise;
if (isPromise(fn)) {
promise = fn;
} else if (isObservable(fn)) {
promise = observableToPromise(fn);
} else if (typeof fn !== 'function') {
fail(this, new AssertionError({
assertion: 'throws',
improperUsage: true,
message: '`t.throws()` must be called with a function, Promise, or Observable',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', fn)]
2017-05-28 00:38:50 +02:00
}));
return;
}
let coreAssertThrowsErrorArg;
if (typeof err === 'string') {
const expectedMessage = err;
coreAssertThrowsErrorArg = error => error.message === expectedMessage;
} else {
// Assume it's a constructor function or regular expression
coreAssertThrowsErrorArg = err;
}
const test = (fn, stack) => {
let actual;
let threw = false;
try {
coreAssert.throws(() => {
try {
fn();
} catch (err) {
actual = err;
threw = true;
throw err;
}
}, coreAssertThrowsErrorArg);
return actual;
} catch (err) {
throw new AssertionError({
assertion: 'throws',
message,
stack,
2017-08-14 05:01:11 +02:00
values: threw ?
[formatWithLabel('Threw unexpected exception:', actual)] :
null
2017-05-28 00:38:50 +02:00
});
}
};
if (promise) {
// Record stack before it gets lost in the promise chain.
const stack = getStack();
2017-08-14 05:01:11 +02:00
const intermediate = promise.then(value => {
throw new AssertionError({
assertion: 'throws',
message: 'Expected promise to be rejected, but it was resolved instead',
values: [formatWithLabel('Resolved with:', value)]
});
}, reason => test(makeRethrow(reason), stack));
2017-05-28 00:38:50 +02:00
pending(this, intermediate);
// Don't reject the returned promise, even if the assertion fails.
return intermediate.catch(noop);
}
try {
const retval = test(fn);
pass(this);
return retval;
} catch (err) {
fail(this, err);
}
},
notThrows(fn, message) {
let promise;
if (isPromise(fn)) {
promise = fn;
} else if (isObservable(fn)) {
promise = observableToPromise(fn);
} else if (typeof fn !== 'function') {
fail(this, new AssertionError({
assertion: 'notThrows',
improperUsage: true,
message: '`t.notThrows()` must be called with a function, Promise, or Observable',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', fn)]
2017-05-28 00:38:50 +02:00
}));
return;
}
const test = (fn, stack) => {
try {
coreAssert.doesNotThrow(fn);
} catch (err) {
throw new AssertionError({
assertion: 'notThrows',
message,
stack,
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Threw:', err.actual)]
2017-05-28 00:38:50 +02:00
});
}
};
if (promise) {
// Record stack before it gets lost in the promise chain.
const stack = getStack();
const intermediate = promise.then(noop, reason => test(makeRethrow(reason), stack));
pending(this, intermediate);
// Don't reject the returned promise, even if the assertion fails.
return intermediate.catch(noop);
}
try {
test(fn);
pass(this);
} catch (err) {
fail(this, err);
}
},
ifError(actual, message) {
if (actual) {
fail(this, new AssertionError({
assertion: 'ifError',
message,
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Error:', actual)]
2017-05-28 00:38:50 +02:00
}));
} else {
pass(this);
}
},
2017-08-14 05:01:11 +02:00
snapshot(expected, optionsOrMessage, message) {
const options = {};
if (typeof optionsOrMessage === 'string') {
message = optionsOrMessage;
} else if (optionsOrMessage) {
options.id = optionsOrMessage.id;
}
options.expected = expected;
options.message = message;
let result;
try {
result = this._test.compareWithSnapshot(options);
} catch (err) {
if (!(err instanceof snapshotManager.SnapshotError)) {
throw err;
}
const improperUsage = {name: err.name, snapPath: err.snapPath};
if (err instanceof snapshotManager.VersionMismatchError) {
improperUsage.snapVersion = err.snapVersion;
improperUsage.expectedVersion = err.expectedVersion;
}
fail(this, new AssertionError({
assertion: 'snapshot',
message: message || 'Could not compare snapshot',
improperUsage
}));
return;
}
2017-05-28 00:38:50 +02:00
if (result.pass) {
pass(this);
2017-08-14 05:01:11 +02:00
} else if (result.actual) {
2017-05-28 00:38:50 +02:00
fail(this, new AssertionError({
assertion: 'snapshot',
message: message || 'Did not match snapshot',
2017-08-14 05:01:11 +02:00
values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})]
}));
} else {
fail(this, new AssertionError({
assertion: 'snapshot',
message: message || 'No snapshot available, run with --update-snapshots'
2017-05-28 00:38:50 +02:00
}));
}
}
};
const enhancedAssertions = enhanceAssert(pass, fail, {
truthy(actual, message) {
if (!actual) {
throw new AssertionError({
assertion: 'truthy',
message,
operator: '!!',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Value is not truthy:', actual)]
2017-05-28 00:38:50 +02:00
});
}
},
falsy(actual, message) {
if (actual) {
throw new AssertionError({
assertion: 'falsy',
message,
operator: '!',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Value is not falsy:', actual)]
2017-05-28 00:38:50 +02:00
});
}
},
true(actual, message) {
if (actual !== true) {
throw new AssertionError({
assertion: 'true',
message,
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Value is not `true`:', actual)]
2017-05-28 00:38:50 +02:00
});
}
},
false(actual, message) {
if (actual !== false) {
throw new AssertionError({
assertion: 'false',
message,
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Value is not `false`:', actual)]
2017-05-28 00:38:50 +02:00
});
}
},
regex(string, regex, message) {
if (typeof string !== 'string') {
throw new AssertionError({
assertion: 'regex',
improperUsage: true,
message: '`t.regex()` must be called with a string',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', string)]
2017-05-28 00:38:50 +02:00
});
}
if (!(regex instanceof RegExp)) {
throw new AssertionError({
assertion: 'regex',
improperUsage: true,
message: '`t.regex()` must be called with a regular expression',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', regex)]
2017-05-28 00:38:50 +02:00
});
}
if (!regex.test(string)) {
throw new AssertionError({
assertion: 'regex',
message,
values: [
2017-08-14 05:01:11 +02:00
formatWithLabel('Value must match expression:', string),
formatWithLabel('Regular expression:', regex)
2017-05-28 00:38:50 +02:00
]
});
}
},
notRegex(string, regex, message) {
if (typeof string !== 'string') {
throw new AssertionError({
assertion: 'notRegex',
improperUsage: true,
message: '`t.notRegex()` must be called with a string',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', string)]
2017-05-28 00:38:50 +02:00
});
}
if (!(regex instanceof RegExp)) {
throw new AssertionError({
assertion: 'notRegex',
improperUsage: true,
message: '`t.notRegex()` must be called with a regular expression',
2017-08-14 05:01:11 +02:00
values: [formatWithLabel('Called with:', regex)]
2017-05-28 00:38:50 +02:00
});
}
if (regex.test(string)) {
throw new AssertionError({
assertion: 'notRegex',
message,
values: [
2017-08-14 05:01:11 +02:00
formatWithLabel('Value must not match expression:', string),
formatWithLabel('Regular expression:', regex)
2017-05-28 00:38:50 +02:00
]
});
}
}
});
return Object.assign(assertions, enhancedAssertions);
}
exports.wrapAssertions = wrapAssertions;