diff --git a/.travis.yml b/.travis.yml index 528d3b729..d1b7241c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ env: - TARGET=travis node_js: - - "0.10" - - "0.12" - - "4" - - "5" - "6" - "7" - "9" @@ -28,4 +24,4 @@ matrix: addons: chrome: stable -script: make $TARGET +script: make $TARGET \ No newline at end of file diff --git a/Makefile b/Makefile index 0c758ba84..1ff1abd62 100644 --- a/Makefile +++ b/Makefile @@ -50,12 +50,9 @@ test-jest: ifeq ($(MODERN_NODE), true) ./node_modules/.bin/jest else - ./node_modules/.bin/jest -c test/jest.es5.config.json + ./node_modules/.bin/jest --rootDir . -c test/jest.es5.config.json endif -test-jest-if-supported-node-version: - @node-version-gte-6 && make test-jest || echo Skipping, jest is unsupported with node $(shell node --version) - .PHONY: test test: test-sources @./node_modules/.bin/mocha --opts $(MOCHA_OPTS) $(TEST_SOURCES) $(TEST_SOURCE_MARKDOWN) @@ -89,7 +86,7 @@ travis-main: clean lint test test-jasmine test-jest coverage - to bar', function(expect, subject) { expect(subject, 'to equal', 'bar'); diff --git a/externaltests/failingAsync.spec.js b/externaltests/failingAsync.spec.js index 1bfc572d4..0ce091c55 100644 --- a/externaltests/failingAsync.spec.js +++ b/externaltests/failingAsync.spec.js @@ -1,4 +1,4 @@ -var expect = require('../lib'); +var expect = require('../lib').clone(); expect.addAssertion(' when delayed a little bit ', function( expect, diff --git a/externaltests/forgotToReturnPromiseRejectedInTheNextTick.spec.js b/externaltests/forgotToReturnPromiseRejectedInTheNextTick.spec.js index 97e7171db..c7f6aae9d 100644 --- a/externaltests/forgotToReturnPromiseRejectedInTheNextTick.spec.js +++ b/externaltests/forgotToReturnPromiseRejectedInTheNextTick.spec.js @@ -1,4 +1,4 @@ -var expect = require('../lib'); +var expect = require('../lib').clone(); it('should fail', () => { expect.addAssertion(' to foo', function(expect, subject) { diff --git a/lib/Unexpected.js b/lib/Unexpected.js index bef52f2dc..ecdb5613f 100644 --- a/lib/Unexpected.js +++ b/lib/Unexpected.js @@ -13,7 +13,6 @@ const defaultDepth = require('./defaultDepth'); const createWrappedExpectProto = require('./createWrappedExpectProto'); const AssertionString = require('./AssertionString'); const throwIfNonUnexpectedError = require('./throwIfNonUnexpectedError'); -const makeDiffResultBackwardsCompatible = require('./makeDiffResultBackwardsCompatible'); function isAssertionArg({ type }) { return type.is('assertion'); @@ -235,6 +234,16 @@ function createExpectIt(unexpected, expectations) { ? context : new Context(unexpected); + if ( + orGroups.length === 1 && + orGroups[0].length === 1 && + orGroups[0][0].length === 1 && + typeof orGroups[0][0][0] === 'function' + ) { + // expect.it(subject => ...) + return oathbreaker(orGroups[0][0][0](subject)); + } + const groupEvaluations = []; const promises = []; orGroups.forEach(orGroup => { @@ -817,15 +826,13 @@ Unexpected.prototype.addType = function(type, childUnexpected) { ); } - return makeDiffResultBackwardsCompatible( - baseType.diff( - actual, - expected, - output.clone(), - (actual, expected) => that.diff(actual, expected, output.clone()), - (value, depth) => output.clone().appendInspected(value, depth), - that.equal.bind(that) - ) + return baseType.diff( + actual, + expected, + output.clone(), + (actual, expected) => that.diff(actual, expected, output.clone()), + (value, depth) => output.clone().appendInspected(value, depth), + that.equal.bind(that) ); }; @@ -1329,7 +1336,7 @@ Unexpected.prototype._expect = function expect(context, args) { if (args.length < 2) { throw new Error('The expect function requires at least two parameters.'); - } else if (testDescriptionString && testDescriptionString._expectIt) { + } else if (typeof testDescriptionString === 'function') { return that.expect.withError( () => testDescriptionString(subject), err => { @@ -1404,7 +1411,7 @@ Unexpected.prototype._expect = function expect(context, args) { wrappedExpect, subject ); - } else if (testDescriptionString && testDescriptionString._expectIt) { + } else if (typeof testDescriptionString === 'function') { wrappedExpect.errorMode = 'nested'; return wrappedExpect.withError( () => testDescriptionString(subject), @@ -1586,16 +1593,14 @@ Unexpected.prototype.diff = function( seen.push(a); } - return makeDiffResultBackwardsCompatible( - this.findCommonType(a, b).diff( - a, - b, - output, - (actual, expected) => - that.diff(actual, expected, output.clone(), recursions - 1, seen), - (v, depth) => output.clone().appendInspected(v, depth), - (actual, expected) => that.equal(actual, expected) - ) + return this.findCommonType(a, b).diff( + a, + b, + output, + (actual, expected) => + that.diff(actual, expected, output.clone(), recursions - 1, seen), + (v, depth) => output.clone().appendInspected(v, depth), + (actual, expected) => that.equal(actual, expected) ); }; diff --git a/lib/UnexpectedError.js b/lib/UnexpectedError.js index b5cead743..24acec176 100644 --- a/lib/UnexpectedError.js +++ b/lib/UnexpectedError.js @@ -1,7 +1,6 @@ const utils = require('./utils'); const defaultDepth = require('./defaultDepth'); const useFullStackTrace = require('./useFullStackTrace'); -const makeDiffResultBackwardsCompatible = require('./makeDiffResultBackwardsCompatible'); const errorMethodBlacklist = [ 'message', @@ -71,14 +70,12 @@ UnexpectedError.prototype.buildDiff = function(options) { const expect = this.expect; return ( this.createDiff && - makeDiffResultBackwardsCompatible( - this.createDiff( - output, - (actual, expected) => expect.diff(actual, expected, output.clone()), - (v, depth) => - output.clone().appendInspected(v, (depth || defaultDepth) - 1), - (actual, expected) => expect.equal(actual, expected) - ) + this.createDiff( + output, + (actual, expected) => expect.diff(actual, expected, output.clone()), + (v, depth) => + output.clone().appendInspected(v, (depth || defaultDepth) - 1), + (actual, expected) => expect.equal(actual, expected) ) ); }; diff --git a/lib/assertions.js b/lib/assertions.js index 6a5afd7b1..768b8434f 100644 --- a/lib/assertions.js +++ b/lib/assertions.js @@ -1080,10 +1080,11 @@ module.exports = expect => { const expected = {}; keys.forEach((key, index) => { if (typeof nextArg === 'string') { - expected[key] = s => expect.shift(s); + expected[key] = expect.it(s => expect.shift(s)); } else if (typeof nextArg === 'function') { - expected[key] = s => - nextArg._expectIt ? nextArg(s, expect.context) : nextArg(s, index); + expected[key] = nextArg._expectIt + ? nextArg + : expect.it(s => nextArg(s, index)); } else { expected[key] = nextArg; } @@ -1492,7 +1493,7 @@ module.exports = expect => { const valueKey = valueType.valueForKey(value, keyInValue); const valueKeyType = expect.findTypeOf(valueKey); - if (valueKeyType.is('function')) { + if (valueKeyType.is('expect.it')) { return valueKey(subjectKey); } else { return expect(subjectKey, 'to [exhaustively] satisfy', valueKey); @@ -1703,7 +1704,9 @@ module.exports = expect => { } else if (type === 'insert') { this.annotationBlock(function() { if ( - expect.findTypeOf(diffItem.value).is('function') + expect + .findTypeOf(diffItem.value) + .is('expect.it') ) { this.error('missing: ').block(function() { this.omitSubject = undefined; @@ -1821,32 +1824,38 @@ module.exports = expect => { if (valueType.is('array-like') && !subjectIsArrayLike) { expect.fail(); } - const promiseByKey = {}; - const keys = valueType.getKeys(value); + const subjectKeys = subjectType.getKeys(subject); + const valueKeys = valueType.getKeys(value); + // calculate the unique keys early given enumerability no + // longer affects what is included in the list of keys + const uniqueKeys = subjectType.uniqueKeys(subjectKeys, valueKeys); - if (!subjectIsArrayLike) { - // Find all non-enumerable subject keys present in value, but not returned by subjectType.getKeys: - keys.forEach(key => { - if ( - Object.prototype.hasOwnProperty.call(subject, key) && - subjectKeys.indexOf(key) === -1 - ) { - subjectKeys.push(key); + const promiseByKey = {}; + let forceExhaustivelyComparison = false; + uniqueKeys.forEach((key, index) => { + const subjectKey = subjectType.valueForKey(subject, key); + const valueKey = valueType.valueForKey(value, key); + const valueKeyType = expect.findTypeOf(valueKey); + const isDefinedSubjectKey = typeof subjectKey !== 'undefined'; + const isDefinedValueKey = typeof valueKey !== 'undefined'; + + if (expect.flags.exhaustively) { + if (valueKeyType.is('expect.it') && !isDefinedSubjectKey) { + // ensure value only expect.it key is marked missing + forceExhaustivelyComparison = true; } - }); - } + } else { + if (isDefinedSubjectKey && !isDefinedValueKey) { + // ignore subject only keys unless we are being exhaustive + return; + } + } - keys.forEach((key, index) => { promiseByKey[key] = expect.promise(() => { - const subjectKey = subjectType.valueForKey(subject, key); - const valueKey = valueType.valueForKey(value, key); - const valueKeyType = expect.findTypeOf(valueKey); if (valueKeyType.is('expect.it')) { expect.context.thisObject = subject; return valueKey(subjectKey, expect.context); - } else if (valueKeyType.is('function')) { - return valueKey(subjectKey); } else { return expect(subjectKey, 'to [exhaustively] satisfy', valueKey); } @@ -1856,25 +1865,8 @@ module.exports = expect => { return expect.promise .all([ expect.promise(() => { - if (expect.flags.exhaustively) { - const nonOwnKeysWithDefinedValues = keys.filter( - key => - !Object.prototype.hasOwnProperty.call(subject, key) && - typeof subjectType.valueForKey(subject, key) !== 'undefined' - ); - const valueKeysWithDefinedValues = keys.filter( - key => typeof valueType.valueForKey(value, key) !== 'undefined' - ); - const subjectKeysWithDefinedValues = subjectKeys.filter( - key => - typeof subjectType.valueForKey(subject, key) !== 'undefined' - ); - expect( - valueKeysWithDefinedValues.length - - nonOwnKeysWithDefinedValues.length, - 'to equal', - subjectKeysWithDefinedValues.length - ); + if (forceExhaustivelyComparison) { + throw new Error('exhaustive comparison failure'); } }), expect.promise.all(promiseByKey) @@ -1885,16 +1877,13 @@ module.exports = expect => { diff(output, diff, inspect, equal) { output.inline = true; const subjectIsArrayLike = subjectType.is('array-like'); - const valueKeys = valueType.getKeys(value); - const keys = subjectType - .uniqueKeys(subjectKeys, valueKeys) - .filter(key => { - // Skip missing keys expected to be missing so they don't get rendered in the diff - return ( - subjectType.hasKey(subject, key) || - typeof valueType.valueForKey(value, key) !== 'undefined' - ); - }); + // Skip missing keys expected to be missing so they don't get rendered in the diff + const keys = uniqueKeys.filter(key => { + return ( + subjectType.hasKey(subject, key) || + typeof valueType.valueForKey(value, key) !== 'undefined' + ); + }); const prefixOutput = subjectType.prefix( output.clone(), subject @@ -1939,7 +1928,7 @@ module.exports = expect => { conflicting = null; } } else if (!subjectType.hasKey(subject, key)) { - if (expect.findTypeOf(valueKey).is('function')) { + if (expect.findTypeOf(valueKey).is('expect.it')) { if (promiseByKey[key].isRejected()) { output.error('// missing:').sp(); valueOutput = output @@ -2388,14 +2377,22 @@ module.exports = expect => { } }); }, - err => + err => { + if ( + err.isOperational && + !err.propertyIsEnumerable('isOperational') + ) { + delete err.isOperational; + } + expect.withError( () => expect.shift(err), e => { e.originalError = err; throw e; } - ) + ); + } ); } ); diff --git a/lib/index.js b/lib/index.js index de1ff88ce..f825a7bc3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,7 +2,8 @@ module.exports = require('./Unexpected') .create() .use(require('./styles')) .use(require('./types')) - .use(require('./assertions')); + .use(require('./assertions')) + .freeze(); // Add an inspect method to all the promises we return that will make the REPL, console.log, and util.inspect render it nicely in node.js: require('unexpected-bluebird').prototype.inspect = function() { diff --git a/lib/makeDiffResultBackwardsCompatible.js b/lib/makeDiffResultBackwardsCompatible.js deleted file mode 100644 index 379a1928f..000000000 --- a/lib/makeDiffResultBackwardsCompatible.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = function makeDiffResultBackwardsCompatible(diff) { - if (diff) { - if (diff.isMagicPen) { - // New format: { [MagicPen], inline: } - // Make backwards compatible by adding a 'diff' property that points - // to the instance itself. - diff.diff = diff; - } else { - // Old format: { inline: , diff: } - // Upgrade to the new format by moving the inline property to - // the magicpen instance, and remain backwards compatibly by adding - // the diff property pointing to the instance itself. - diff.diff.inline = diff.inline; - diff = diff.diff; - diff.diff = diff; - } - } - return diff; -}; diff --git a/lib/types.js b/lib/types.js index 38b53f9cc..6b3e9559a 100644 --- a/lib/types.js +++ b/lib/types.js @@ -114,7 +114,7 @@ module.exports = function(expect) { }, getKeys: Object.getOwnPropertySymbols ? obj => { - const keys = Object.keys(obj); + const keys = Object.getOwnPropertyNames(obj); const symbols = Object.getOwnPropertySymbols(obj); if (symbols.length > 0) { return keys.concat(symbols); @@ -122,7 +122,7 @@ module.exports = function(expect) { return keys; } } - : Object.keys, + : Object.getOwnPropertyNames, // If Symbol support is not detected default to undefined which, when // passed to Array.prototype.sort, means "natural" (asciibetical) sort. keyComparator: @@ -154,44 +154,7 @@ module.exports = function(expect) { } : undefined, equal(a, b, equal) { - if (a === b) { - return true; - } - - if (b.constructor !== a.constructor) { - return false; - } - - const actualKeys = this.getKeys(a).filter( - key => typeof this.valueForKey(a, key) !== 'undefined' - ); - const expectedKeys = this.getKeys(b).filter( - key => typeof this.valueForKey(b, key) !== 'undefined' - ); - - // having the same number of owned properties (keys incorporates hasOwnProperty) - if (actualKeys.length !== expectedKeys.length) { - return false; - } - //the same set of keys (although not necessarily the same order), - actualKeys.sort(this.keyComparator); - expectedKeys.sort(this.keyComparator); - // cheap key test - for (let i = 0; i < actualKeys.length; i += 1) { - if (actualKeys[i] !== expectedKeys[i]) { - return false; - } - } - - //equivalent values for every corresponding key, and - // possibly expensive deep test - for (let j = 0; j < actualKeys.length; j += 1) { - const key = actualKeys[j]; - if (!equal(this.valueForKey(a, key), this.valueForKey(b, key))) { - return false; - } - } - return true; + return utils.checkObjectEqualityUsingType(a, b, this, equal); }, hasKey(obj, key) { return key in obj; @@ -800,12 +763,20 @@ module.exports = function(expect) { 'sourceId', 'sourceURL', 'stack', - 'stackArray' + 'stackArray', + '__stackCleaned__', + 'isOperational' // added by the promise implementation ].reduce((result, prop) => { result[prop] = true; return result; }, {}); + if (new Error().hasOwnProperty('arguments')) { + // node.js 0.10 adds two extra non-enumerable properties to Error instances: + errorMethodBlacklist.arguments = true; + errorMethodBlacklist.type = true; + } + expect.addType({ base: 'object', name: 'Error', @@ -827,7 +798,9 @@ module.exports = function(expect) { }, equal(a, b, equal) { return ( - a === b || (equal(a.message, b.message) && this.baseType.equal(a, b)) + a === b || + (equal(a.message, b.message) && + utils.checkObjectEqualityUsingType(a, b, this, equal)) ); }, inspect(value, depth, output, inspect) { diff --git a/lib/utils.js b/lib/utils.js index 9cb0545c9..6836d6b8e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -27,6 +27,48 @@ const utils = (module.exports = { return a === b; }), + checkObjectEqualityUsingType(a, b, type, isEqual) { + if (a === b) { + return true; + } + + if (b.constructor !== a.constructor) { + return false; + } + + const actualKeys = type + .getKeys(a) + .filter(key => typeof type.valueForKey(a, key) !== 'undefined'); + const expectedKeys = type + .getKeys(b) + .filter(key => typeof type.valueForKey(b, key) !== 'undefined'); + + // having the same number of owned properties (keys incorporates hasOwnProperty) + if (actualKeys.length !== expectedKeys.length) { + return false; + } + + //the same set of keys (although not necessarily the same order), + actualKeys.sort(type.keyComparator); + expectedKeys.sort(type.keyComparator); + // cheap key test + for (let i = 0; i < actualKeys.length; i += 1) { + if (actualKeys[i] !== expectedKeys[i]) { + return false; + } + } + + //equivalent values for every corresponding key, and + // possibly expensive deep test + for (let j = 0; j < actualKeys.length; j += 1) { + const key = actualKeys[j]; + if (!isEqual(type.valueForKey(a, key), type.valueForKey(b, key))) { + return false; + } + } + return true; + }, + duplicateArrayLikeUsingType(obj, type) { const keys = type.getKeys(obj); diff --git a/package.json b/package.json index 370c1d29d..ac843d73f 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,13 @@ "babel-preset-es2015-without-strict": "^0.0.4", "coveralls": "^3.0.0", "es5-shim": "^4.5.9", - "eslint": "^4.17.0", - "eslint-config-pretty-standard": "^1.2.0", + "eslint": "^5.10.0", + "eslint-config-pretty-standard": "^2.0.0", "eslint-plugin-import": "^2.8.0", "eslint-plugin-mocha": "^5.2.0", - "eslint-plugin-prettier": "^2.6.0", + "eslint-plugin-prettier": "^3.0.0", "find-node-modules": "^1.0.4", - "gh-pages": "^2.0.1", + "gh-pages": "^2.0.0", "istanbul": "^0.4.5", "jasmine": "~3.2.0", "jasmine-core": "^3.1.0", @@ -54,20 +54,20 @@ "karma-chrome-launcher": "2.2.0", "karma-mocha": "1.3.0", "minimist": "^1.2.0", - "mocha": "^3.2.0", + "mocha": "^5.2.0", "mocha-slow-reporter": "^*", "node-version-check": "^2.2.0", - "nyc": "^10.3.2", + "nyc": "^13.0.1", "offline-github-changelog": "^1.2.0", "prettier": "~1.15.1", "rollup": "^0.68.1", "rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-node-globals": "^1.1.0", "rollup-plugin-node-resolve": "^4.0.0", - "rollup-plugin-uglify": "^4.0.0", + "rollup-plugin-uglify": "^6.0.0", "rsvp": "^4.7.0", "serve": "*", - "unexpected-documentation-site-generator": "^4.6.1", + "unexpected-documentation-site-generator": "^5.0.0", "unexpected-magicpen": "^1.0.0", "unexpected-markdown": "^1.7.4" }, diff --git a/test/api/addType.spec.js b/test/api/addType.spec.js index 2a9b9acb6..17124894a 100644 --- a/test/api/addType.spec.js +++ b/test/api/addType.spec.js @@ -220,48 +220,6 @@ describe('addType', () => { ); }); }); - - it('allows adding a type whose diff method returns an old-style { inline: , diff: } object', () => { - clonedExpect.addType({ - name: 'box', - identify(obj) { - return obj && typeof obj === 'object' && obj.isBox; - }, - equal(a, b, equal) { - return a === b || equal(a.value, b.value); - }, - inspect(obj, depth, output, inspect) { - return output - .text('box(') - .append(inspect(obj.value)) - .text(')'); - }, - diff(actual, expected, output, diff) { - return { - inline: true, - diff: output - .text('box(') - .append(diff({ value: actual.value }, { value: expected.value })) - .text(')') - }; - } - }); - - expect( - function() { - clonedExpect(box('abc'), 'to equal', box('abe')); - }, - 'to throw', - "expected box('abc') to equal box('abe')\n" + - '\n' + - 'box({\n' + - " value: 'abc' // should equal 'abe'\n" + - ' //\n' + - ' // -abc\n' + - ' // +abe\n' + - '})' - ); - }); }); describe('#inspect', () => { diff --git a/test/api/fail.spec.js b/test/api/fail.spec.js index aa6b58638..abbd15346 100644 --- a/test/api/fail.spec.js +++ b/test/api/fail.spec.js @@ -150,26 +150,5 @@ describe('fail assertion', () => { "expected 'bar' to foo\n" + '\n' + 'custom' ); }); - - it('should support a diff function that uses the old API', () => { - var clonedExpect = expect.clone(); - clonedExpect.addAssertion(' to foo', function(expect, subject) { - expect.fail({ - diff(output, diff, inspect, equal) { - return { - inline: false, - diff: output.text('custom') - }; - } - }); - }); - expect( - function() { - clonedExpect('bar', 'to foo'); - }, - 'to throw', - "expected 'bar' to foo\n" + '\n' + 'custom' - ); - }); }); }); diff --git a/test/api/inspect.spec.js b/test/api/inspect.spec.js index c240dc58f..70c06718d 100644 --- a/test/api/inspect.spec.js +++ b/test/api/inspect.spec.js @@ -207,9 +207,9 @@ describe('inspect', () => { deeply: { nested: { object: 'This should not be shown' }, string: 'should be shown', - 'a list': [1, 2, 3] + list: [1, 2, 3] }, - 'a list': [1, 2, 3] + list: [1, 2, 3] } } } @@ -269,8 +269,8 @@ describe('inspect', () => { ' circular: { self: [Circular] },\n' + ' this: {\n' + ' is: {\n' + - " deeply: { nested: ..., string: 'should be shown', 'a list': ... },\n" + - " 'a list': [ 1, 2, 3 ]\n" + + " deeply: { nested: ..., string: 'should be shown', list: ... },\n" + + ' list: [ 1, 2, 3 ]\n' + ' }\n' + ' }\n' + ' }\n' + @@ -321,7 +321,7 @@ describe('inspect', () => { " { id: 4, name: 'Barbara Lynn' },\n" + " { id: 5, name: 'Sharpe Downs' }\n" + ' ],\n' + - " circular: { self: [Circular] }, this: { is: { deeply: { nested: ..., string: 'should be shown', 'a list': ... }, 'a list': [ 1, 2, 3 ] } }\n" + + " circular: { self: [Circular] }, this: { is: { deeply: { nested: ..., string: 'should be shown', list: ... }, list: [ 1, 2, 3 ] } }\n" + ' }\n' + ']' ); diff --git a/test/api/it.spec.js b/test/api/it.spec.js index 9b31e9129..5a6813690 100644 --- a/test/api/it.spec.js +++ b/test/api/it.spec.js @@ -346,14 +346,14 @@ describe('expect.it', () => { describe('when passed a function', () => { it('should succeed', () => { - expect.it(function(value) { + expect.it(value => { expect(value, 'to equal', 'foo'); })('foo'); }); it('should fail with a diff', () => { expect( - function() { + () => { expect.it(function(value) { expect(value, 'to equal', 'bar'); })('foo'); @@ -363,9 +363,22 @@ describe('expect.it', () => { ); }); + it('supports returning a promise from the function', () => { + return expect( + () => + expect.it(value => + expect.promise(run => { + setTimeout(run(() => expect(value, 'to equal', 'bar'))); + }) + )('foo'), + 'to be rejected with', + "expected 'foo' to equal 'bar'\n" + '\n' + '-foo\n' + '+bar' + ); + }); + it('should fail when passed more than two arguments', () => { expect( - function() { + () => { expect.it(function(value) { expect(value, 'to equal', 'bar'); }, 'yadda')('foo'); @@ -374,5 +387,45 @@ describe('expect.it', () => { 'expect.it() does not accept additional arguments' ); }); + + describe('and chained the expression is chained', () => { + describe('and the first expression is a function', () => { + it('fails with a diff including all items in the chain', () => { + expect( + () => { + expect + .it(function(value) { + expect(value, 'to equal', 'bar'); + }) + .and('to be a string')('foo'); + }, + 'to throw', + "⨯ expected 'foo' to equal 'bar' and\n" + + '\n' + + ' -foo\n' + + ' +bar\n' + + "✓ expected 'foo' to be a string" + ); + }); + }); + + describe('and the second expression is a function', () => { + it('fails with a diff including all items in the chain', () => { + expect( + () => { + expect.it('to be a string').and(function(value) { + expect(value, 'to equal', 'bar'); + })('foo'); + }, + 'to throw', + "✓ expected 'foo' to be a string and\n" + + "⨯ expected 'foo' to equal 'bar'\n" + + '\n' + + ' -foo\n' + + ' +bar' + ); + }); + }); + }); }); }); diff --git a/test/assertions/to-have-property.spec.js b/test/assertions/to-have-property.spec.js index 25bed92f0..fe79e2380 100644 --- a/test/assertions/to-have-property.spec.js +++ b/test/assertions/to-have-property.spec.js @@ -42,21 +42,24 @@ describe('to have property assertion', () => { expect(subject, 'to have enumerable property', 'enumFalse'); }, 'to throw exception', - "expected { a: 'b' } to have enumerable property 'enumFalse'" + "expected { a: 'b', enumFalse: 't', configFalse: 't', writableFalse: 't' }\n" + + "to have enumerable property 'enumFalse'" ); expect( function() { expect(subject, 'to have configurable property', 'configFalse'); }, 'to throw exception', - "expected { a: 'b' } to have configurable property 'configFalse'" + "expected { a: 'b', enumFalse: 't', configFalse: 't', writableFalse: 't' }\n" + + "to have configurable property 'configFalse'" ); expect( function() { expect(subject, 'to have writable property', 'writableFalse'); }, 'to throw exception', - "expected { a: 'b' } to have writable property 'writableFalse'" + "expected { a: 'b', enumFalse: 't', configFalse: 't', writableFalse: 't' }\n" + + "to have writable property 'writableFalse'" ); }); }); diff --git a/test/assertions/to-satisfy.spec.js b/test/assertions/to-satisfy.spec.js index 560156400..042896015 100644 --- a/test/assertions/to-satisfy.spec.js +++ b/test/assertions/to-satisfy.spec.js @@ -680,6 +680,33 @@ describe('to satisfy assertion', () => { }); }); + describe('with a expect.it function wrapper', () => { + it("succeeds if the function doesn't throw", () => { + expect({ foo: 'bar' }, 'to satisfy', { + foo: expect.it(v => expect(v, 'to be a string')) + }); + }); + + it('should fail with an diff if the function fails', () => { + expect( + () => { + expect({ foo: 3 }, 'to satisfy', { + foo: expect.it(function(v) { + expect(v, 'to equal', 2); + }) + }); + }, + 'to throw', + 'expected { foo: 3 }\n' + + "to satisfy { foo: expect.it(function (v) { expect(v, 'to equal', 2); }) }\n" + + '\n' + + '{\n' + + ' foo: 3 // should equal 2\n' + + '}' + ); + }); + }); + describe('with a synchronous expect.it in the RHS object', () => { it('should support an object with a property value of expect.it', () => { expect({ foo: 'bar' }, 'to satisfy', { @@ -2281,7 +2308,8 @@ describe('to satisfy assertion', () => { }); }, 'to throw', - "expected {} to exhaustively satisfy { nonEnumerable: 'wrong' }\n" + + "expected { nonEnumerable: 'theValue' }\n" + + "to exhaustively satisfy { nonEnumerable: 'wrong' }\n" + '\n' + '{\n' + ' nonEnumerable:\n' + @@ -2295,10 +2323,6 @@ describe('to satisfy assertion', () => { }); describe('when not matching the non-enumerable property', () => { - it('should succeed', () => { - expect(bar, 'to exhaustively satisfy', {}); - }); - it('should fail with a diff', () => { expect( function() { @@ -2307,9 +2331,11 @@ describe('to satisfy assertion', () => { }); }, 'to throw', - "expected {} to exhaustively satisfy { somethingElse: 'wrong' }\n" + + "expected { nonEnumerable: 'theValue' }\n" + + "to exhaustively satisfy { somethingElse: 'wrong' }\n" + '\n' + '{\n' + + " nonEnumerable: 'theValue' // should be removed\n" + " // missing somethingElse: 'wrong'\n" + '}' ); @@ -2318,23 +2344,26 @@ describe('to satisfy assertion', () => { }); }); - // Debatable: - describe('when an unpresent value to is satisfied against a function', () => { - it('should allow an unpresent value to be satisfied against a non-expect.it function', () => { - expect({}, 'to satisfy', { foo: function() {} }); + describe('when an unpresent value to is satisfied against an expect.it function wrapper', () => { + it('should allow an unpresent value to be satisfied against the function', () => { + expect({}, 'to satisfy', { + foo: expect.it(v => { + expect(v, 'to be undefined'); + }) + }); }); it('should fail when the function throws', () => { expect( - function() { + () => { expect({}, 'to satisfy', { - foo(value) { + foo: expect.it(value => { expect(value, 'to be a string'); - } + }) }); }, 'to throw', - function(err) { + err => { // Compensate for V8 5.1+ setting { foo: function () {} }.foo.name === 'foo' // http://v8project.blogspot.dk/2016/04/v8-release-51.html expect( @@ -2357,7 +2386,7 @@ describe('to satisfy assertion', () => { it('should fail with a diff', () => { expect( - function() { + () => { expect({}, 'to satisfy', { foo: expect.it('to be a string') }); }, 'to throw', @@ -2372,11 +2401,16 @@ describe('to satisfy assertion', () => { it('should not break when the assertion fails and there is a fulfilled function in the RHS', () => { expect( - function() { - expect({}, 'to satisfy', { bar: 123, foo: function() {} }); + () => { + expect({}, 'to satisfy', { + bar: 123, + foo: expect.it(function(v) { + expect(v, 'to be undefined'); + }) + }); }, 'to throw', - function(err) { + err => { // Compensate for V8 5.1+ setting { foo: function () {} }.foo.name === 'foo' // http://v8project.blogspot.dk/2016/04/v8-release-51.html expect( @@ -2385,17 +2419,40 @@ describe('to satisfy assertion', () => { .toString() .replace(/function foo/g, 'function '), 'to satisfy', - 'expected {} to satisfy { bar: 123, foo: function () {} }\n' + + 'expected {} to satisfy\n' + + '{\n' + + ' bar: 123,\n' + + " foo: expect.it(function (v) { expect(v, 'to be undefined'); })\n" + + '}\n' + '\n' + '{\n' + ' // missing bar: 123\n' + - ' // missing foo: should satisfy function () {}\n' + + " // missing foo: should satisfy expect.it(function (v) { expect(v, 'to be undefined'); })\n" + '}' ); } ); }); + it('should render a diff when the function differs', () => { + function myFunction() {} + function myOtherFunction() {} + + expect( + () => { + expect({ foo: myFunction }, 'to satisfy', { foo: myOtherFunction }); + }, + 'to throw', + 'expected { foo: function myFunction() {} }\n' + + 'to satisfy { foo: function myOtherFunction() {} }\n' + + '\n' + + '{\n' + + ' foo: function myFunction() {} // expected { foo: function myFunction() {} }\n' + + ' // to satisfy { foo: function myOtherFunction() {} }\n' + + '}' + ); + }); + describe('when matching the constructor property of an object', () => { function Foo() {} diff --git a/test/external.spec.js b/test/external.spec.js index 9f2519f64..650b6c4a1 100644 --- a/test/external.spec.js +++ b/test/external.spec.js @@ -245,44 +245,122 @@ if (typeof process === 'object') { }); }); - // jest requires node.js 6 or above: - if (!/^v[012345]\./.test(process.version)) { - describe('executed through jest', () => { - expect.addAssertion( - ' executed through jest ', - function(expect, subject, env) { - if (!Array.isArray(subject)) { - subject = [subject]; - } - return expect.promise(function(run) { - childProcess.execFile( - pathModule.resolve(basePath, 'node_modules', '.bin', 'jest'), - [ - '--config', - pathModule.resolve(externaltestsDir, 'jestconfig.json') - ].concat( - subject.map(function(fileName) { - return pathModule.resolve( - externaltestsDir, - `${fileName}.spec.js` - ); - }) - ), - { - cwd: basePath, - env: extend({}, process.env, env || {}) - }, - run(function(err, stdout, stderr) { - return [err, stdout, stderr]; - }) - ); - }); + describe('executed through jest', () => { + expect.addAssertion( + ' executed through jest ', + function(expect, subject, env) { + if (!Array.isArray(subject)) { + subject = [subject]; } - ); + return expect.promise(function(run) { + childProcess.execFile( + pathModule.resolve(basePath, 'node_modules', '.bin', 'jest'), + [ + '--config', + pathModule.resolve(externaltestsDir, 'jestconfig.json') + ].concat( + subject.map(function(fileName) { + return pathModule.resolve( + externaltestsDir, + `${fileName}.spec.js` + ); + }) + ), + { + cwd: basePath, + env: extend({}, process.env, env || {}) + }, + run(function(err, stdout, stderr) { + return [err, stdout, stderr]; + }) + ); + }); + } + ); + + it('should report that a promise was created, but not returned by the it block', () => { + return expect( + 'forgotToReturnPendingPromiseFromSuccessfulItBlock', + 'executed through jest' + ).spread(function(err, stdout, stderr) { + expect( + stderr, + 'to contain', + 'should call the callback: You have created a promise that was not returned from the it block' + ); + expect(err, 'to satisfy', { code: 1 }); + }); + }); + + it('should not report that a promise was created if the test already failed synchronously', () => { + return expect( + 'forgotToReturnPendingPromiseFromFailingItBlock', + 'executed through jest' + ).spread(function(err, stdout, stderr) { + expect( + stderr, + 'not to contain', + 'should call the callback: You have created a promise that was not returned from the it block' + ); + expect(err, 'to satisfy', { code: 1 }); + }); + }); + + it('should trim unexpected plugins from the stack trace when the UNEXPECTED_FULL_TRACE environment variable is not set', () => { + return expect('fullTrace', 'executed through jest', { + UNEXPECTED_FULL_TRACE: '' + }).spread(function(err, stdout, stderr) { + expect(stderr, 'not to contain', 'node_modules/unexpected-bogus/'); + expect(err, 'to satisfy', { code: 1 }); + }); + }); + + it('should not trim unexpected plugins from the stack trace when the UNEXPECTED_FULL_TRACE environment variable is set', () => { + return expect('fullTrace', 'executed through jest', { + UNEXPECTED_FULL_TRACE: 'yes' + }).spread(function(err, stdout, stderr) { + expect(stderr, 'to contain', 'node_modules/unexpected-bogus/'); + expect(err, 'to satisfy', { code: 1 }); + }); + }); + + it('should accept an UNEXPECTED_DEPTH environment variable', () => { + return expect('deepObject', 'executed through jest', { + UNEXPECTED_DEPTH: 6 + }).spread(function(err, stdout, stderr) { + expect(err, 'to be falsy'); + }); + }); - it('should report that a promise was created, but not returned by the it block', () => { + it('should render a long stack trace for an async test', () => { + return expect('failingAsync', 'executed through jest').spread(function( + err, + stdout, + stderr + ) { + expect(err, 'to be truthy'); + expect(stderr, 'to contain', 'From previous event:'); + }); + }); + + it('should fail when a promise failing in the next tick is created but not returned', () => { + return expect( + 'forgotToReturnPromiseRejectedInTheNextTick', + 'executed through jest' + ).spread(function(err, stdout, stderr) { + expect( + stderr, + 'to contain', + 'should fail: You have created a promise that was not returned from the it block' + ); + expect(err, 'to satisfy', { code: 1 }); + }); + }); + + describe('with a test suite spanning multiple files', () => { + it('should report that a promise was created, but not returned by the it block in the first test', () => { return expect( - 'forgotToReturnPendingPromiseFromSuccessfulItBlock', + ['forgotToReturnPendingPromiseFromSuccessfulItBlock', 'successful'], 'executed through jest' ).spread(function(err, stdout, stderr) { expect( @@ -294,126 +372,40 @@ if (typeof process === 'object') { }); }); - it('should not report that a promise was created if the test already failed synchronously', () => { + it('should report that a promise was created, but not returned by the it block in the second test', () => { return expect( - 'forgotToReturnPendingPromiseFromFailingItBlock', + ['successful', 'forgotToReturnPendingPromiseFromSuccessfulItBlock'], 'executed through jest' ).spread(function(err, stdout, stderr) { expect( stderr, - 'not to contain', + 'to contain', 'should call the callback: You have created a promise that was not returned from the it block' ); expect(err, 'to satisfy', { code: 1 }); }); }); + }); - it('should trim unexpected plugins from the stack trace when the UNEXPECTED_FULL_TRACE environment variable is not set', () => { - return expect('fullTrace', 'executed through jest', { - UNEXPECTED_FULL_TRACE: '' - }).spread(function(err, stdout, stderr) { - expect(stderr, 'not to contain', 'node_modules/unexpected-bogus/'); - expect(err, 'to satisfy', { code: 1 }); - }); - }); - - it('should not trim unexpected plugins from the stack trace when the UNEXPECTED_FULL_TRACE environment variable is set', () => { - return expect('fullTrace', 'executed through jest', { - UNEXPECTED_FULL_TRACE: 'yes' - }).spread(function(err, stdout, stderr) { - expect(stderr, 'to contain', 'node_modules/unexpected-bogus/'); - expect(err, 'to satisfy', { code: 1 }); - }); - }); - - it('should accept an UNEXPECTED_DEPTH environment variable', () => { - return expect('deepObject', 'executed through jest', { - UNEXPECTED_DEPTH: 6 - }).spread(function(err, stdout, stderr) { - expect(err, 'to be falsy'); - }); - }); - - it('should render a long stack trace for an async test', () => { - return expect('failingAsync', 'executed through jest').spread( - function(err, stdout, stderr) { - expect(err, 'to be truthy'); - expect(stderr, 'to contain', 'From previous event:'); - } - ); - }); - - it('should fail when a promise failing in the next tick is created but not returned', () => { + describe('with an assertion that succeeds, but creates a promise that remains pending', () => { + it('should pass', () => { return expect( - 'forgotToReturnPromiseRejectedInTheNextTick', + 'assertionSucceedsWhilePromiseIsPending', 'executed through jest' ).spread(function(err, stdout, stderr) { expect( stderr, - 'to contain', - 'should fail: You have created a promise that was not returned from the it block' + 'not to contain', + 'should call the callback: You have created a promise that was not returned from the it block' ); - expect(err, 'to satisfy', { code: 1 }); - }); - }); - - describe('with a test suite spanning multiple files', () => { - it('should report that a promise was created, but not returned by the it block in the first test', () => { - return expect( - [ - 'forgotToReturnPendingPromiseFromSuccessfulItBlock', - 'successful' - ], - 'executed through jest' - ).spread(function(err, stdout, stderr) { - expect( - stderr, - 'to contain', - 'should call the callback: You have created a promise that was not returned from the it block' - ); - expect(err, 'to satisfy', { code: 1 }); - }); - }); - - it('should report that a promise was created, but not returned by the it block in the second test', () => { - return expect( - [ - 'successful', - 'forgotToReturnPendingPromiseFromSuccessfulItBlock' - ], - 'executed through jest' - ).spread(function(err, stdout, stderr) { - expect( - stderr, - 'to contain', - 'should call the callback: You have created a promise that was not returned from the it block' - ); - expect(err, 'to satisfy', { code: 1 }); - }); - }); - }); - - describe('with an assertion that succeeds, but creates a promise that remains pending', () => { - it('should pass', () => { - return expect( - 'assertionSucceedsWhilePromiseIsPending', - 'executed through jest' - ).spread(function(err, stdout, stderr) { - expect( - stderr, - 'not to contain', - 'should call the callback: You have created a promise that was not returned from the it block' - ); - expect(err, 'to be falsy'); - }); + expect(err, 'to be falsy'); }); }); + }); - it('should render the stack trace of the thrown error without any artifacts when "not to error" encounters an error', () => { - return expect( - 'notToErrorCaughtError', - 'executed through jest' - ).spread(function(err, stdout, stderr) { + it('should render the stack trace of the thrown error without any artifacts when "not to error" encounters an error', () => { + return expect('notToErrorCaughtError', 'executed through jest').spread( + function(err, stdout, stderr) { expect(err, 'to satisfy', { code: 1 }); expect( stderr, @@ -421,9 +413,9 @@ if (typeof process === 'object') { ' not to error\n' + " returned promise rejected with: Error('argh')\n" ).and('to contain', ' at thisIsImportant'); - }); - }); + } + ); }); - } + }); }); } diff --git a/test/jest.es5.config.json b/test/jest.es5.config.json index 8d7d2ac5e..02a063955 100644 --- a/test/jest.es5.config.json +++ b/test/jest.es5.config.json @@ -1,5 +1,5 @@ { - "setupFiles": ["/build/test/promisePolyfill"], + "setupFiles": ["/build/test/promisePolyfill.js"], "setupTestFrameworkScriptFile": "/build/test/common.js", "testMatch": ["/build/test/**.spec.js"] } diff --git a/test/types/error-type.spec.js b/test/types/error-type.spec.js index b25aaae29..e3f00434d 100644 --- a/test/types/error-type.spec.js +++ b/test/types/error-type.spec.js @@ -65,11 +65,17 @@ describe('Error type', () => { } function MyError(message) { - Error.call(this); + var instance = new Error(message); + var proto = Object.getPrototypeOf(this); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(instance, proto); + } else { + instance.__proto__ = proto; // eslint-disable-line no-proto + } if (isIE) { - this.name = 'MyError'; + instance.name = 'MyError'; } - this.message = message; + return instance; } inherits(MyError, Error); @@ -147,4 +153,16 @@ describe('Error type', () => { }); }); }); + + describe('when comparing Error objects with differing enumerable keys', () => { + it('should not break', () => { + var e1 = new Error('foo'); + var e2 = new Error(); + e2.message = 'foo'; + + expect(() => { + expect(e1, 'to equal', e2); + }, 'not to throw'); + }); + }); }); diff --git a/test/types/function-type.spec.js b/test/types/function-type.spec.js index 4dd015124..578b460b8 100644 --- a/test/types/function-type.spec.js +++ b/test/types/function-type.spec.js @@ -14,15 +14,11 @@ describe('function type', () => { expect(fn, 'to inspect as', 'function foo() {}'); }); - var isNodeJs3OrBelow = - typeof process === 'object' && /^v[0123]\./.test(process.version); - var isIE = typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Trident') !== -1; - if (!isNodeJs3OrBelow && !isIE) { - // Node.js 3 and below and IE11 don't include "bound ". + if (!isIE) { // For now let's just disable these tests in those environments it('should inspect an anonymous bound function correctly', () => { diff --git a/test/unexpected.spec.js b/test/unexpected.spec.js index 54aa6b871..19944c48b 100644 --- a/test/unexpected.spec.js +++ b/test/unexpected.spec.js @@ -1,4 +1,4 @@ -/*global unexpected*/ +/*global unexpected, weknowhow*/ it.skipIf = function(condition) { (condition ? it.skip : it).apply( @@ -10,6 +10,19 @@ it.skipIf = function(condition) { describe('unexpected', () => { var expect = unexpected.clone(); + it('should freeze the top-level unexpected instance', () => { + const topLevelExpect = + typeof weknowhow === 'undefined' ? require('../lib/') : weknowhow.expect; + + expect( + function() { + topLevelExpect.addAssertion(' to foo', function() {}); + }, + 'to throw', + 'Cannot add an assertion to a frozen instance, please run .clone() first' + ); + }); + describe('argument validation', () => { it('fails when given no parameters', () => { expect(