123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- 'use strict';
- const concordance = require('concordance');
- const observableToPromise = require('observable-to-promise');
- const isError = require('is-error');
- const isObservable = require('is-observable');
- const isPromise = require('is-promise');
- const concordanceOptions = require('./concordance-options').default;
- const concordanceDiffOptions = require('./concordance-options').diff;
- const enhanceAssert = require('./enhance-assert');
- 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));
- }
- const hasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
- 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 || [];
- // 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;
- // Reserved for power-assert statements
- this.statements = [];
- if (opts.stack) {
- this.stack = opts.stack;
- } else {
- const limitBefore = Error.stackTraceLimit;
- Error.stackTraceLimit = Infinity;
- Error.captureStackTrace(this);
- Error.stackTraceLimit = limitBefore;
- }
- }
- }
- exports.AssertionError = AssertionError;
- function getStack() {
- const limitBefore = Error.stackTraceLimit;
- Error.stackTraceLimit = Infinity;
- const obj = {};
- Error.captureStackTrace(obj, getStack);
- Error.stackTraceLimit = limitBefore;
- return obj.stack;
- }
- function wrapAssertions(callbacks) {
- const pass = callbacks.pass;
- const pending = callbacks.pending;
- const fail = callbacks.fail;
- const noop = () => {};
- const assertions = {
- pass() {
- pass(this);
- },
- fail(message) {
- fail(this, new AssertionError({
- assertion: 'fail',
- message: message || 'Test failed via `t.fail()`'
- }));
- },
- is(actual, expected, message) {
- if (Object.is(actual, expected)) {
- pass(this);
- } else {
- const result = concordance.compare(actual, expected, concordanceOptions);
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
- const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
- if (result.pass) {
- fail(this, new AssertionError({
- assertion: 'is',
- message,
- raw: {actual, expected},
- values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)]
- }));
- } else {
- fail(this, new AssertionError({
- assertion: 'is',
- message,
- raw: {actual, expected},
- values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
- }));
- }
- }
- },
- not(actual, expected, message) {
- if (Object.is(actual, expected)) {
- fail(this, new AssertionError({
- assertion: 'not',
- message,
- raw: {actual, expected},
- values: [formatWithLabel('Value is the same as:', actual)]
- }));
- } else {
- pass(this);
- }
- },
- deepEqual(actual, expected, message) {
- const result = concordance.compare(actual, expected, concordanceOptions);
- if (result.pass) {
- pass(this);
- } else {
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
- const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
- fail(this, new AssertionError({
- assertion: 'deepEqual',
- message,
- raw: {actual, expected},
- values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
- }));
- }
- },
- notDeepEqual(actual, expected, message) {
- const result = concordance.compare(actual, expected, concordanceOptions);
- if (result.pass) {
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
- fail(this, new AssertionError({
- assertion: 'notDeepEqual',
- message,
- raw: {actual, expected},
- values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)]
- }));
- } else {
- pass(this);
- }
- },
- throws(thrower, expected, message) { // eslint-disable-line complexity
- if (typeof thrower !== 'function' && !isPromise(thrower) && !isObservable(thrower)) {
- fail(this, new AssertionError({
- assertion: 'throws',
- improperUsage: true,
- message: '`t.throws()` must be called with a function, observable or promise',
- values: [formatWithLabel('Called with:', thrower)]
- }));
- return;
- }
- if (typeof expected === 'function') {
- expected = {instanceOf: expected};
- } else if (typeof expected === 'string' || expected instanceof RegExp) {
- expected = {message: expected};
- } else if (arguments.length === 1 || expected === null) {
- expected = {};
- } else if (typeof expected !== 'object' || Array.isArray(expected) || Object.keys(expected).length === 0) {
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The second argument to `t.throws()` must be a function, string, regular expression, expectation object or `null`',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- } else {
- if (hasOwnProperty(expected, 'instanceOf') && typeof expected.instanceOf !== 'function') {
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The `instanceOf` property of the second argument to `t.throws()` must be a function',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- }
- if (hasOwnProperty(expected, 'message') && typeof expected.message !== 'string' && !(expected.message instanceof RegExp)) {
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The `message` property of the second argument to `t.throws()` must be a string or regular expression',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- }
- if (hasOwnProperty(expected, 'name') && typeof expected.name !== 'string') {
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The `name` property of the second argument to `t.throws()` must be a string',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- }
- if (hasOwnProperty(expected, 'code') && typeof expected.code !== 'string') {
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The `code` property of the second argument to `t.throws()` must be a string',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- }
- for (const key of Object.keys(expected)) {
- switch (key) {
- case 'instanceOf':
- case 'is':
- case 'message':
- case 'name':
- case 'code':
- continue;
- default:
- fail(this, new AssertionError({
- assertion: 'throws',
- message: 'The second argument to `t.throws()` contains unexpected properties',
- values: [formatWithLabel('Called with:', expected)]
- }));
- return;
- }
- }
- }
- // Note: this function *must* throw exceptions, since it can be used
- // as part of a pending assertion for observables and promises.
- const assertExpected = (actual, prefix, stack) => {
- if (!isError(actual)) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [formatWithLabel(`${prefix} exception that is not an error:`, actual)]
- });
- }
- if (hasOwnProperty(expected, 'is') && actual !== expected.is) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected to be strictly equal to:', expected.is)
- ]
- });
- }
- if (expected.instanceOf && !(actual instanceof expected.instanceOf)) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected instance of:', expected.instanceOf)
- ]
- });
- }
- if (typeof expected.name === 'string' && actual.name !== expected.name) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected name to equal:', expected.name)
- ]
- });
- }
- if (typeof expected.message === 'string' && actual.message !== expected.message) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected message to equal:', expected.message)
- ]
- });
- }
- if (expected.message instanceof RegExp && !expected.message.test(actual.message)) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected message to match:', expected.message)
- ]
- });
- }
- if (typeof expected.code === 'string' && actual.code !== expected.code) {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [
- formatWithLabel(`${prefix} unexpected exception:`, actual),
- formatWithLabel('Expected code to equal:', expected.code)
- ]
- });
- }
- };
- const handleObservable = (observable, wasReturned) => {
- // Record stack before it gets lost in the promise chain.
- const stack = getStack();
- const intermediate = observableToPromise(observable).then(value => {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [formatWithLabel(`${wasReturned ? 'Returned observable' : 'Observable'} completed with:`, value)]
- });
- }, reason => {
- assertExpected(reason, `${wasReturned ? 'Returned observable' : 'Observable'} errored with`, stack);
- return reason;
- });
- pending(this, intermediate);
- // Don't reject the returned promise, even if the assertion fails.
- return intermediate.catch(noop);
- };
- const handlePromise = (promise, wasReturned) => {
- // Record stack before it gets lost in the promise chain.
- const stack = getStack();
- const intermediate = promise.then(value => {
- throw new AssertionError({
- assertion: 'throws',
- message,
- stack,
- values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)]
- });
- }, reason => {
- assertExpected(reason, `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`, stack);
- return reason;
- });
- pending(this, intermediate);
- // Don't reject the returned promise, even if the assertion fails.
- return intermediate.catch(noop);
- };
- if (isPromise(thrower)) {
- return handlePromise(thrower, false);
- }
- if (isObservable(thrower)) {
- return handleObservable(thrower, false);
- }
- let retval;
- let actual;
- let threw = false;
- try {
- retval = thrower();
- } catch (err) {
- actual = err;
- threw = true;
- }
- if (!threw) {
- if (isPromise(retval)) {
- return handlePromise(retval, true);
- }
- if (isObservable(retval)) {
- return handleObservable(retval, true);
- }
- fail(this, new AssertionError({
- assertion: 'throws',
- message,
- values: [formatWithLabel('Function returned:', retval)]
- }));
- return;
- }
- try {
- assertExpected(actual, 'Function threw');
- pass(this);
- return actual;
- } catch (err) {
- fail(this, err);
- }
- },
- notThrows(nonThrower, message) {
- if (typeof nonThrower !== 'function' && !isPromise(nonThrower) && !isObservable(nonThrower)) {
- fail(this, new AssertionError({
- assertion: 'notThrows',
- improperUsage: true,
- message: '`t.notThrows()` must be called with a function, observable or promise',
- values: [formatWithLabel('Called with:', nonThrower)]
- }));
- return;
- }
- const handleObservable = (observable, wasReturned) => {
- // Record stack before it gets lost in the promise chain.
- const stack = getStack();
- const intermediate = observableToPromise(observable).then(noop, reason => {
- throw new AssertionError({
- assertion: 'notThrows',
- message,
- stack,
- values: [formatWithLabel(`${wasReturned ? 'Returned observable' : 'Observable'} errored with:`, reason)]
- });
- });
- pending(this, intermediate);
- // Don't reject the returned promise, even if the assertion fails.
- return intermediate.catch(noop);
- };
- const handlePromise = (promise, wasReturned) => {
- // Record stack before it gets lost in the promise chain.
- const stack = getStack();
- const intermediate = promise.then(noop, reason => {
- throw new AssertionError({
- assertion: 'notThrows',
- message,
- stack,
- values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, reason)]
- });
- });
- pending(this, intermediate);
- // Don't reject the returned promise, even if the assertion fails.
- return intermediate.catch(noop);
- };
- if (isPromise(nonThrower)) {
- return handlePromise(nonThrower, false);
- }
- if (isObservable(nonThrower)) {
- return handleObservable(nonThrower, false);
- }
- let retval;
- try {
- retval = nonThrower();
- } catch (err) {
- fail(this, new AssertionError({
- assertion: 'notThrows',
- message,
- values: [formatWithLabel(`Function threw:`, err)]
- }));
- return;
- }
- if (isPromise(retval)) {
- return handlePromise(retval, true);
- }
- if (isObservable(retval)) {
- return handleObservable(retval, true);
- }
- pass(this);
- },
- 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.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;
- }
- if (result.pass) {
- pass(this);
- } else if (result.actual) {
- fail(this, new AssertionError({
- assertion: 'snapshot',
- message: message || 'Did not match snapshot',
- values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})]
- }));
- } else {
- fail(this, new AssertionError({
- assertion: 'snapshot',
- message: message || 'No snapshot available, run with --update-snapshots'
- }));
- }
- }
- };
- const enhancedAssertions = enhanceAssert(pass, fail, {
- truthy(actual, message) {
- if (!actual) {
- throw new AssertionError({
- assertion: 'truthy',
- message,
- operator: '!!',
- values: [formatWithLabel('Value is not truthy:', actual)]
- });
- }
- },
- falsy(actual, message) {
- if (actual) {
- throw new AssertionError({
- assertion: 'falsy',
- message,
- operator: '!',
- values: [formatWithLabel('Value is not falsy:', actual)]
- });
- }
- },
- true(actual, message) {
- if (actual !== true) {
- throw new AssertionError({
- assertion: 'true',
- message,
- values: [formatWithLabel('Value is not `true`:', actual)]
- });
- }
- },
- false(actual, message) {
- if (actual !== false) {
- throw new AssertionError({
- assertion: 'false',
- message,
- values: [formatWithLabel('Value is not `false`:', actual)]
- });
- }
- },
- regex(string, regex, message) {
- if (typeof string !== 'string') {
- throw new AssertionError({
- assertion: 'regex',
- improperUsage: true,
- message: '`t.regex()` must be called with a string',
- values: [formatWithLabel('Called with:', string)]
- });
- }
- if (!(regex instanceof RegExp)) {
- throw new AssertionError({
- assertion: 'regex',
- improperUsage: true,
- message: '`t.regex()` must be called with a regular expression',
- values: [formatWithLabel('Called with:', regex)]
- });
- }
- if (!regex.test(string)) {
- throw new AssertionError({
- assertion: 'regex',
- message,
- values: [
- formatWithLabel('Value must match expression:', string),
- formatWithLabel('Regular expression:', regex)
- ]
- });
- }
- },
- notRegex(string, regex, message) {
- if (typeof string !== 'string') {
- throw new AssertionError({
- assertion: 'notRegex',
- improperUsage: true,
- message: '`t.notRegex()` must be called with a string',
- values: [formatWithLabel('Called with:', string)]
- });
- }
- if (!(regex instanceof RegExp)) {
- throw new AssertionError({
- assertion: 'notRegex',
- improperUsage: true,
- message: '`t.notRegex()` must be called with a regular expression',
- values: [formatWithLabel('Called with:', regex)]
- });
- }
- if (regex.test(string)) {
- throw new AssertionError({
- assertion: 'notRegex',
- message,
- values: [
- formatWithLabel('Value must not match expression:', string),
- formatWithLabel('Regular expression:', regex)
- ]
- });
- }
- }
- });
- return Object.assign(assertions, enhancedAssertions);
- }
- exports.wrapAssertions = wrapAssertions;
|