diff --git a/lib/assert.js b/lib/assert.js index 9868d6224..33dd64693 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -1,5 +1,6 @@ 'use strict'; const concordance = require('concordance'); +const empower = require('empower-core'); const isError = require('is-error'); const isPromise = require('is-promise'); const concordanceOptions = require('./concordance-options').default; @@ -27,6 +28,10 @@ function formatWithLabel(label, value) { } const hasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); +const noop = () => {}; +const notImplemented = () => { + throw new Error('not implemented'); +}; class AssertionError extends Error { constructor(opts) { @@ -229,39 +234,65 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } } -function wrapAssertions(callbacks) { - const {pass, pending, fail} = callbacks; - const noop = () => {}; +class Assertions { + constructor({ + pass = notImplemented, + pending = notImplemented, + fail = notImplemented, + skip = notImplemented, + compareWithSnapshot = notImplemented + } = {}) { + const withSkip = assertionFn => { + assertionFn.skip = skip; + return assertionFn; + }; + + // When adding new enhanced functions with new patterns, don't forget to add to + // https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json + // Then release a new version of that preset and bump the SemVer range here. + const withPowerAssert = (pattern, assertionFn) => empower(assertionFn, { + onError: event => { + if (event.powerAssertContext) { + event.error.statements = enhanceAssert.formatter(event.powerAssertContext); + } - const assertions = { - pass() { - pass(this); - }, + fail(event.error); + }, + onSuccess: () => { + pass(); + }, + bindReceiver: false, + patterns: [pattern] + }); - fail(message) { - fail(this, new AssertionError({ + this.pass = withSkip(() => { + pass(); + }); + + this.fail = withSkip(message => { + fail(new AssertionError({ assertion: 'fail', message: message || 'Test failed via `t.fail()`' })); - }, + }); - is(actual, expected, message) { + this.is = withSkip((actual, expected, message) => { if (Object.is(actual, expected)) { - pass(this); + pass(); } 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({ + fail(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({ + fail(new AssertionError({ assertion: 'is', message, raw: {actual, expected}, @@ -269,55 +300,59 @@ function wrapAssertions(callbacks) { })); } } - }, + }); - not(actual, expected, message) { + this.not = withSkip((actual, expected, message) => { if (Object.is(actual, expected)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'not', message, raw: {actual, expected}, values: [formatWithLabel('Value is the same as:', actual)] })); } else { - pass(this); + pass(); } - }, + }); - deepEqual(actual, expected, message) { + this.deepEqual = withSkip((actual, expected, message) => { const result = concordance.compare(actual, expected, concordanceOptions); if (result.pass) { - pass(this); + pass(); } else { const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions); const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions); - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'deepEqual', message, raw: {actual, expected}, values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)] })); } - }, + }); - notDeepEqual(actual, expected, message) { + this.notDeepEqual = withSkip((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({ + fail(new AssertionError({ assertion: 'notDeepEqual', message, raw: {actual, expected}, values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)] })); } else { - pass(this); + pass(); } - }, + }); - throws(fn, expectations, message) { + this.throws = withSkip((...args) => { + // Since arrow functions do not support 'arguments', we are using rest + // operator, so we can determine the total number of arguments passed + // to the function. + let [fn, expectations, message] = args; if (typeof fn !== 'function') { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'throws', improperUsage: true, message: '`t.throws()` must be called with a function', @@ -327,23 +362,20 @@ function wrapAssertions(callbacks) { } try { - expectations = validateExpectations('throws', expectations, arguments.length); + expectations = validateExpectations('throws', expectations, args.length); } catch (error) { - fail(this, error); + fail(error); return; } let retval; - let actual; - let threw = false; + let actual = null; try { retval = fn(); if (isPromise(retval)) { - try { - retval.catch(noop); - } catch (_) {} - - fail(this, new AssertionError({ + // Here isPromise() checks if something is "promise like". Cast to an actual promise. + Promise.resolve(retval).catch(noop); + fail(new AssertionError({ assertion: 'throws', message, values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)] @@ -352,11 +384,10 @@ function wrapAssertions(callbacks) { } } catch (error) { actual = error; - threw = true; } - if (!threw) { - fail(this, new AssertionError({ + if (!actual) { + fail(new AssertionError({ assertion: 'throws', message, values: [formatWithLabel('Function returned:', retval)] @@ -372,16 +403,17 @@ function wrapAssertions(callbacks) { message, prefix: 'Function threw' }); - pass(this); + pass(); return actual; } catch (error) { - fail(this, error); + fail(error); } - }, + }); - throwsAsync(thrower, expectations, message) { + this.throwsAsync = withSkip((...args) => { + let [thrower, expectations, message] = args; if (typeof thrower !== 'function' && !isPromise(thrower)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'throwsAsync', improperUsage: true, message: '`t.throwsAsync()` must be called with a function or promise', @@ -391,16 +423,17 @@ function wrapAssertions(callbacks) { } try { - expectations = validateExpectations('throwsAsync', expectations, arguments.length); + expectations = validateExpectations('throwsAsync', expectations, args.length); } catch (error) { - fail(this, error); + fail(error); return Promise.resolve(); } const handlePromise = (promise, wasReturned) => { // Record stack before it gets lost in the promise chain. const stack = getStack(); - const intermediate = promise.then(value => { // eslint-disable-line promise/prefer-await-to-then + // Handle "promise like" objects by casting to a real Promise. + const intermediate = Promise.resolve(promise).then(value => { // eslint-disable-line promise/prefer-await-to-then throw new AssertionError({ assertion: 'throwsAsync', message, @@ -419,7 +452,7 @@ function wrapAssertions(callbacks) { return reason; }); - pending(this, intermediate); + pending(intermediate); // Don't reject the returned promise, even if the assertion fails. return intermediate.catch(noop); }; @@ -429,17 +462,15 @@ function wrapAssertions(callbacks) { } let retval; - let actual; - let threw = false; + let actual = null; try { retval = thrower(); } catch (error) { actual = error; - threw = true; } - if (threw) { - fail(this, new AssertionError({ + if (actual) { + fail(new AssertionError({ assertion: 'throwsAsync', message, actualStack: actual.stack, @@ -452,17 +483,17 @@ function wrapAssertions(callbacks) { return handlePromise(retval, true); } - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'throwsAsync', message, values: [formatWithLabel('Function returned:', retval)] })); return Promise.resolve(); - }, + }); - notThrows(fn, message) { + this.notThrows = withSkip((fn, message) => { if (typeof fn !== 'function') { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notThrows', improperUsage: true, message: '`t.notThrows()` must be called with a function', @@ -474,7 +505,7 @@ function wrapAssertions(callbacks) { try { fn(); } catch (error) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notThrows', message, actualStack: error.stack, @@ -483,12 +514,12 @@ function wrapAssertions(callbacks) { return; } - pass(this); - }, + pass(); + }); - notThrowsAsync(nonThrower, message) { + this.notThrowsAsync = withSkip((nonThrower, message) => { if (typeof nonThrower !== 'function' && !isPromise(nonThrower)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notThrowsAsync', improperUsage: true, message: '`t.notThrowsAsync()` must be called with a function or promise', @@ -500,15 +531,16 @@ function wrapAssertions(callbacks) { const handlePromise = (promise, wasReturned) => { // Record stack before it gets lost in the promise chain. const stack = getStack(); - const intermediate = promise.then(noop, reason => { // eslint-disable-line promise/prefer-await-to-then + // Handle "promise like" objects by casting to a real Promise. + const intermediate = Promise.resolve(promise).then(noop, error => { // eslint-disable-line promise/prefer-await-to-then throw new AssertionError({ assertion: 'notThrowsAsync', message, actualStack: stack, - values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, reason)] + values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)] }); }); - pending(this, intermediate); + pending(intermediate); // Don't reject the returned promise, even if the assertion fails. return intermediate.catch(noop); }; @@ -521,7 +553,7 @@ function wrapAssertions(callbacks) { try { retval = nonThrower(); } catch (error) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notThrowsAsync', message, actualStack: error.stack, @@ -531,7 +563,7 @@ function wrapAssertions(callbacks) { } if (!isPromise(retval)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notThrowsAsync', message, values: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)] @@ -540,9 +572,9 @@ function wrapAssertions(callbacks) { } return handlePromise(retval, true); - }, + }); - snapshot(expected, optionsOrMessage, message) { + this.snapshot = withSkip((expected, optionsOrMessage, message) => { const options = {}; if (typeof optionsOrMessage === 'string') { message = optionsOrMessage; @@ -555,7 +587,7 @@ function wrapAssertions(callbacks) { let result; try { - result = this.compareWithSnapshot(options); + result = compareWithSnapshot(options); } catch (error) { if (!(error instanceof snapshotManager.SnapshotError)) { throw error; @@ -567,7 +599,7 @@ function wrapAssertions(callbacks) { improperUsage.expectedVersion = error.expectedVersion; } - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'snapshot', message: message || 'Could not compare snapshot', improperUsage @@ -576,74 +608,74 @@ function wrapAssertions(callbacks) { } if (result.pass) { - pass(this); + pass(); } else if (result.actual) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'snapshot', message: message || 'Did not match snapshot', values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})] })); } else { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'snapshot', message: message || 'No snapshot available, run with --update-snapshots' })); } - }, + }); - truthy(actual, message) { + this.truthy = withSkip((actual, message) => { if (actual) { - pass(this); + pass(); } else { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'truthy', message, operator: '!!', values: [formatWithLabel('Value is not truthy:', actual)] })); } - }, + }); - falsy(actual, message) { + this.falsy = withSkip((actual, message) => { if (actual) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'falsy', message, operator: '!', values: [formatWithLabel('Value is not falsy:', actual)] })); } else { - pass(this); + pass(); } - }, + }); - true(actual, message) { + this.true = withSkip((actual, message) => { if (actual === true) { - pass(this); + pass(); } else { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'true', message, values: [formatWithLabel('Value is not `true`:', actual)] })); } - }, + }); - false(actual, message) { + this.false = withSkip((actual, message) => { if (actual === false) { - pass(this); + pass(); } else { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'false', message, values: [formatWithLabel('Value is not `false`:', actual)] })); } - }, + }); - regex(string, regex, message) { + this.regex = withSkip((string, regex, message) => { if (typeof string !== 'string') { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'regex', improperUsage: true, message: '`t.regex()` must be called with a string', @@ -653,7 +685,7 @@ function wrapAssertions(callbacks) { } if (!(regex instanceof RegExp)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'regex', improperUsage: true, message: '`t.regex()` must be called with a regular expression', @@ -663,7 +695,7 @@ function wrapAssertions(callbacks) { } if (!regex.test(string)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'regex', message, values: [ @@ -674,12 +706,12 @@ function wrapAssertions(callbacks) { return; } - pass(this); - }, + pass(); + }); - notRegex(string, regex, message) { + this.notRegex = withSkip((string, regex, message) => { if (typeof string !== 'string') { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notRegex', improperUsage: true, message: '`t.notRegex()` must be called with a string', @@ -689,7 +721,7 @@ function wrapAssertions(callbacks) { } if (!(regex instanceof RegExp)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notRegex', improperUsage: true, message: '`t.notRegex()` must be called with a regular expression', @@ -699,7 +731,7 @@ function wrapAssertions(callbacks) { } if (regex.test(string)) { - fail(this, new AssertionError({ + fail(new AssertionError({ assertion: 'notRegex', message, values: [ @@ -710,24 +742,22 @@ function wrapAssertions(callbacks) { return; } - pass(this); - } - }; - - const enhancedAssertions = enhanceAssert(pass, fail, { - assert(actual, message) { - if (!actual) { - throw new AssertionError({ - assertion: 'assert', - message, - operator: '!!', - values: [formatWithLabel('Value is not truthy:', actual)] - }); - } - } - }); + pass(); + }); - return Object.assign(assertions, enhancedAssertions); + this.assert = withSkip(withPowerAssert( + 'assert(value, [message])', + (actual, message) => { + if (!actual) { + throw new AssertionError({ + assertion: 'assert', + message, + operator: '!!', + values: [formatWithLabel('Value is not truthy:', actual)] + }); + } + }) + ); + } } - -exports.wrapAssertions = wrapAssertions; +exports.Assertions = Assertions; diff --git a/lib/enhance-assert.js b/lib/enhance-assert.js index 88ace2290..e15f3ec95 100644 --- a/lib/enhance-assert.js +++ b/lib/enhance-assert.js @@ -4,13 +4,6 @@ const dotProp = require('dot-prop'); const generate = require('@babel/generator').default; const concordanceOptions = require('./concordance-options').default; -// When adding patterns, don't forget to add to -// https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json -// Then release a new version of that preset and bump the SemVer range here. -const PATTERNS = [ - 't.assert(value, [message])' -]; - const computeStatement = node => generate(node).code; const getNode = (ast, path) => dotProp.get(ast, path.replace(/\//g, '.')); @@ -27,24 +20,4 @@ const formatter = context => { .reverse(); }; -const enhanceAssert = (pass, fail, assertions) => { - const empower = require('empower-core'); - return empower(assertions, { - destructive: true, - onError(event) { - const {error} = event; - if (event.powerAssertContext) { // Context may be missing in internal tests. - error.statements = formatter(event.powerAssertContext); - } - - fail(this, error); - }, - onSuccess() { - pass(this); - }, - patterns: PATTERNS, - bindReceiver: false - }); -}; - -module.exports = enhanceAssert; +module.exports.formatter = formatter; diff --git a/lib/test.js b/lib/test.js index dd7e9828f..a24884bcb 100644 --- a/lib/test.js +++ b/lib/test.js @@ -22,65 +22,51 @@ const captureStack = start => { return obj.stack; }; -const assertions = assert.wrapAssertions({ - pass(test) { - test.countPassedAssertion(); - }, - - pending(test, promise) { - test.addPendingAssertion(promise); - }, - - fail(test, error) { - test.addFailedAssertion(error); - } -}); -const assertionNames = Object.keys(assertions); - -function log(...inputArgs) { - const args = inputArgs.map(value => { - return typeof value === 'string' ? - value : - concordance.format(value, concordanceOptions); - }); - - if (args.length > 0) { - this.addLog(args.join(' ')); - } -} - -function plan(count) { - this.plan(count, captureStack(this.plan)); -} - -function timeout(ms) { - this.timeout(ms); -} - const testMap = new WeakMap(); -class ExecutionContext { +class ExecutionContext extends assert.Assertions { constructor(test) { + super({ + pass: () => { + test.countPassedAssertion(); + }, + pending: promise => { + test.addPendingAssertion(promise); + }, + fail: err => { + test.addFailedAssertion(err); + }, + skip: () => { + test.countPassedAssertion(); + }, + compareWithSnapshot: options => { + return test.compareWithSnapshot(options); + } + }); testMap.set(this, test); - const skip = () => { - test.countPassedAssertion(); + this.snapshot.skip = () => { + test.skipSnapshot(); }; - const boundPlan = plan.bind(test); - boundPlan.skip = () => {}; - - Object.defineProperties(this, assertionNames.reduce((props, name) => { - props[name] = {value: assertions[name].bind(test)}; - props[name].value.skip = skip; - return props; - }, { - log: {value: log.bind(test)}, - plan: {value: boundPlan}, - timeout: {value: timeout.bind(test)} - })); + this.log = (...inputArgs) => { + const args = inputArgs.map(value => { + return typeof value === 'string' ? + value : + concordance.format(value, concordanceOptions); + }); + if (args.length > 0) { + test.addLog(args.join(' ')); + } + }; - this.snapshot.skip = () => { - test.skipSnapshot(); + this.plan = count => { + test.plan(count, captureStack(test.plan)); + }; + + this.plan.skip = () => {}; + + this.timeout = ms => { + test.timeout(ms); }; } diff --git a/test/assert.js b/test/assert.js index 83566ce63..1ed0da746 100644 --- a/test/assert.js +++ b/test/assert.js @@ -9,40 +9,32 @@ const renderer = require('react-test-renderer'); const {test} = require('tap'); const assert = require('../lib/assert'); const snapshotManager = require('../lib/snapshot-manager'); -const Test = require('../lib/test'); const HelloMessage = require('./fixture/hello-message'); let lastFailure = null; let lastPassed = false; -const assertions = assert.wrapAssertions({ - pass(testObj) { - if (testObj !== assertions && !(testObj instanceof Test)) { - throw new Error('Expected testObj'); - } - - lastPassed = true; - }, - - pending(testObj, promise) { - if (testObj !== assertions && !(testObj instanceof Test)) { - throw new Error('Expected testObj'); - } - promise.then(() => { - lastPassed = true; - }, err => { - lastFailure = err; +const assertions = new class extends assert.Assertions { + constructor(overwrites = {}) { + super({ + pass: () => { + lastPassed = true; + }, + pending: promise => { + promise.then(() => { + lastPassed = true; + }, err => { + lastFailure = err; + }); + }, + fail: error => { + lastFailure = error; + }, + skip: () => {}, + ...overwrites }); - }, - - fail(testObj, error) { - if (testObj !== assertions && !(testObj instanceof Test)) { - throw new Error('Expected testObj'); - } - - lastFailure = error; } -}); +}(); function assertFailure(t, subset) { if (!lastFailure) { @@ -178,6 +170,11 @@ test('.pass()', t => { assertions.pass(); }); + passes(t, () => { + const {pass} = assertions; + pass(); + }); + t.end(); }); @@ -196,6 +193,14 @@ test('.fail()', t => { message: 'my message' }); + failsWith(t, () => { + const {fail} = assertions; + fail(); + }, { + assertion: 'fail', + message: 'Test failed via `t.fail()`' + }); + t.end(); }); @@ -204,6 +209,11 @@ test('.is()', t => { assertions.is('foo', 'foo'); }); + passes(t, () => { + const {is} = assertions; + is('foo', 'foo'); + }); + passes(t, () => { assertions.is('', ''); }); @@ -381,6 +391,11 @@ test('.not()', t => { assertions.not('foo', 'bar'); }); + passes(t, () => { + const {not} = assertions; + not('foo', 'bar'); + }); + fails(t, () => { assertions.not(NaN, NaN); }); @@ -427,6 +442,11 @@ test('.deepEqual()', t => { }); }); + passes(t, () => { + const {deepEqual} = assertions; + deepEqual({a: 'a', b: 'b'}, {b: 'b', a: 'a'}); + }); + passes(t, () => { assertions.deepEqual({ a: 'a', @@ -670,6 +690,11 @@ test('.notDeepEqual()', t => { assertions.notDeepEqual({a: 'a'}, {a: 'b'}); }); + passes(t, () => { + const {notDeepEqual} = assertions; + notDeepEqual({a: 'a'}, {a: 'b'}); + }); + passes(t, () => { assertions.notDeepEqual(['a', 'b'], ['c', 'd']); }); @@ -708,6 +733,15 @@ test('.throws()', gather(t => { values: [{label: 'Function returned:', formatted: /undefined/}] }); + failsWith(t, () => { + const {throws} = assertions; + throws(() => {}); + }, { + assertion: 'throws', + message: '', + values: [{label: 'Function returned:', formatted: /undefined/}] + }); + // Fails because function doesn't throw. Asserts that 'my message' is used // as the assertion message (*not* compared against the error). failsWith(t, () => { @@ -866,6 +900,27 @@ test('.throws()', gather(t => { throw err; }, {code: 42}); }); + + // Regression test for https://github.com/avajs/ava/issues/1676 + fails(t, () => { + assertions.throws(() => { + throw new Error('foo'); + }, false); + }); + + // Regression test for https://github.com/avajs/ava/issues/1676 + passes(t, () => { + assertions.throws(() => { + throw new Error('foo'); + }, null); + }); + + // Regression test for https://github.com/avajs/ava/issues/1676 + fails(t, () => { + assertions.throws(() => { + throw new Error('foo'); + }, undefined); + }); })); test('.throws() returns the thrown error', t => { @@ -887,6 +942,15 @@ test('.throwsAsync()', gather(t => { values: [{label: 'Promise resolved with:', formatted: /'foo'/}] }); + eventuallyFailsWith(t, () => { + const {throwsAsync} = assertions; + return throwsAsync(Promise.resolve('foo')); + }, { + assertion: 'throwsAsync', + message: '', + values: [{label: 'Promise resolved with:', formatted: /'foo'/}] + }); + // Fails because the promise is resolved with an Error eventuallyFailsWith(t, () => assertions.throwsAsync(Promise.resolve(new Error())), { assertion: 'throwsAsync', @@ -1127,6 +1191,11 @@ test('.notThrows()', gather(t => { assertions.notThrows(() => {}); }); + passes(t, () => { + const {notThrows} = assertions; + notThrows(() => {}); + }); + // Fails because the function throws. failsWith(t, () => { assertions.notThrows(() => { @@ -1155,6 +1224,11 @@ test('.notThrowsAsync()', gather(t => { // Passes because the promise is resolved eventuallyPasses(t, () => assertions.notThrowsAsync(Promise.resolve())); + eventuallyPasses(t, () => { + const {notThrowsAsync} = assertions; + return notThrowsAsync(Promise.resolve()); + }); + // Fails because the promise is rejected eventuallyFailsWith(t, () => assertions.notThrowsAsync(Promise.reject(new Error())), { assertion: 'notThrowsAsync', @@ -1245,28 +1319,57 @@ test('.snapshot()', t => { fixedLocation: null, updating }); - const setup = title => { - return new Test({ - title, - compareTestSnapshot: options => manager.compare(options) - }); + const setup = _title => { + return new class extends assertions.constructor { + constructor(title) { + super({ + compareWithSnapshot: assertionOptions => { + return manager.compare({ + belongsTo: assertionOptions.id || this.title, + expected: assertionOptions.expected, + index: assertionOptions.id ? 0 : this.snapshotInvocationCount++, + label: assertionOptions.id ? '' : assertionOptions.message || `Snapshot ${this.snapshotInvocationCount}` + }); + } + }); + this.title = title; + this.snapshotInvocationCount = 0; + } + }(_title); }; - passes(t, () => { - const testInstance = setup('passes'); - assertions.snapshot.call(testInstance, {foo: 'bar'}); - assertions.snapshot.call(testInstance, {foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report'); - assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'})); - assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); - }); + { + const assertions = setup('passes'); + + passes(t, () => { + assertions.snapshot({foo: 'bar'}); + }); + + passes(t, () => { + const {snapshot} = assertions; + snapshot({foo: 'bar'}); + }); + + passes(t, () => { + assertions.snapshot({foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report'); + }); + + passes(t, () => { + assertions.snapshot(React.createElement(HelloMessage, {name: 'Sindre'})); + }); + + passes(t, () => { + assertions.snapshot(renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); + }); + } { - const testInstance = setup('fails'); + const assertions = setup('fails'); if (updating) { - assertions.snapshot.call(testInstance, {foo: 'bar'}); + assertions.snapshot({foo: 'bar'}); } else { failsWith(t, () => { - assertions.snapshot.call(testInstance, {foo: 'not bar'}); + assertions.snapshot({foo: 'not bar'}); }, { assertion: 'snapshot', message: 'Did not match snapshot', @@ -1276,8 +1379,8 @@ test('.snapshot()', t => { } failsWith(t, () => { - const testInstance = setup('fails (fixed id)'); - assertions.snapshot.call(testInstance, {foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report'); + const assertions = setup('fails (fixed id)'); + assertions.snapshot({foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report'); }, { assertion: 'snapshot', message: 'different message, also not included in snapshot report', @@ -1285,12 +1388,12 @@ test('.snapshot()', t => { }); { - const testInstance = setup('fails'); + const assertions = setup('fails'); if (updating) { - assertions.snapshot.call(testInstance, {foo: 'bar'}, 'my message'); + assertions.snapshot({foo: 'bar'}, 'my message'); } else { failsWith(t, () => { - assertions.snapshot.call(testInstance, {foo: 'not bar'}, 'my message'); + assertions.snapshot({foo: 'not bar'}, 'my message'); }, { assertion: 'snapshot', message: 'my message', @@ -1300,23 +1403,23 @@ test('.snapshot()', t => { } { - const testInstance = setup('rendered comparison'); + const assertions = setup('rendered comparison'); if (updating) { - assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); + assertions.snapshot(renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); } else { passes(t, () => { - assertions.snapshot.call(testInstance, React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre'))); + assertions.snapshot(React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre'))); }); } } { - const testInstance = setup('rendered comparison'); + const assertions = setup('rendered comparison'); if (updating) { - assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); + assertions.snapshot(renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON()); } else { failsWith(t, () => { - assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON()); + assertions.snapshot(renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON()); }, { assertion: 'snapshot', message: 'Did not match snapshot', @@ -1326,12 +1429,12 @@ test('.snapshot()', t => { } { - const testInstance = setup('element comparison'); + const assertions = setup('element comparison'); if (updating) { - assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'})); + assertions.snapshot(React.createElement(HelloMessage, {name: 'Sindre'})); } else { failsWith(t, () => { - assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Vadim'})); + assertions.snapshot(React.createElement(HelloMessage, {name: 'Vadim'})); }, { assertion: 'snapshot', message: 'Did not match snapshot', @@ -1368,6 +1471,12 @@ test('.truthy()', t => { assertions.truthy(true); }); + passes(t, () => { + const {truthy} = assertions; + truthy(1); + truthy(true); + }); + t.end(); }); @@ -1395,6 +1504,12 @@ test('.falsy()', t => { assertions.falsy(false); }); + passes(t, () => { + const {falsy} = assertions; + falsy(0); + falsy(false); + }); + t.end(); }); @@ -1435,6 +1550,11 @@ test('.true()', t => { assertions.true(true); }); + passes(t, () => { + const {true: trueFn} = assertions; + trueFn(true); + }); + t.end(); }); @@ -1475,6 +1595,11 @@ test('.false()', t => { assertions.false(false); }); + passes(t, () => { + const {false: falseFn} = assertions; + falseFn(false); + }); + t.end(); }); @@ -1483,6 +1608,11 @@ test('.regex()', t => { assertions.regex('abc', /^abc$/); }); + passes(t, () => { + const {regex} = assertions; + regex('abc', /^abc$/); + }); + failsWith(t, () => { assertions.regex('foo', /^abc$/); }, { @@ -1534,6 +1664,11 @@ test('.notRegex()', t => { assertions.notRegex('abc', /def/); }); + passes(t, () => { + const {notRegex} = assertions; + notRegex('abc', /def/); + }); + failsWith(t, () => { assertions.notRegex('abc', /abc/); }, { @@ -1603,5 +1738,11 @@ test('.assert()', t => { assertions.assert(true); }); + passes(t, () => { + const {assert} = assertions; + assert(1); + assert(true); + }); + t.end(); }); diff --git a/test/fixture/assert.js.md b/test/fixture/assert.js.md index effe453d7..a3d865488 100644 --- a/test/fixture/assert.js.md +++ b/test/fixture/assert.js.md @@ -1,4 +1,4 @@ -# Snapshot report for `test/assert.js` +# Snapshot report for `assert.js` The actual snapshot is saved in `assert.js.snap`. @@ -36,11 +36,17 @@ Generated by [AVA](https://ava.li). > Snapshot 2 + { + foo: 'bar', + } + +> Snapshot 3 + -> Snapshot 3 +> Snapshot 4
Hello diff --git a/test/fixture/assert.js.snap b/test/fixture/assert.js.snap index 371b3be27..8295b2b7b 100644 Binary files a/test/fixture/assert.js.snap and b/test/fixture/assert.js.snap differ diff --git a/test/test.js b/test/test.js index 847836b97..e594278d4 100644 --- a/test/test.js +++ b/test/test.js @@ -737,6 +737,7 @@ test('snapshot assertion can be skipped', t => { title: 'passes', fn(t) { t.snapshot.skip({not: {a: 'match'}}); + t.snapshot.skip({not: {b: 'match'}}); t.snapshot(React.createElement(HelloMessage, {name: 'Sindre'})); } }).run().then(result => { @@ -797,3 +798,54 @@ test('timeout is refreshed on assert', t => { t.is(result.passed, true); }); }); + +test('.log() is bound', t => { + return ava(a => { + const {log} = a; + [1, 2, 3].forEach(val => { + log('value: ' + val); + }); + ['value foo', 'value bar'].forEach(val => log(val)); + }).run().then(result => { + t.deepEqual(result.logs, [ + 'value: 1', + 'value: 2', + 'value: 3', + 'value foo', + 'value bar' + ]); + }); +}); + +test('.plan() is bound', t => { + return ava(a => { + const {plan} = a; + plan(3); + + a.pass(); + a.is(2, 2); + a.truthy('string'); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('.timeout() is bound', t => { + return ava(a => { + const {timeout} = a; + timeout(10); + a.pass(); + }).run().then(result => { + t.true(result.passed); + }); +}); + +test('.end() is bound', t => { + return ava.cb(a => { + const {end} = a; + a.pass(); + end(); + }).run().then(result => { + t.true(result.passed); + }); +});