From 1841618b1c1fd63ee231f12dcdf5248f2a9f5c36 Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Wed, 11 Jan 2017 17:40:05 -0200 Subject: [PATCH] Throw error when instanceof is passed something that is not a function as constructor --- lib/chai/core/assertions.js | 26 +++++++++- package.json | 2 +- test/assert.js | 95 +++++++++++++++++++++++++++++++++++++ test/expect.js | 87 ++++++++++++++++++++++++++++----- test/should.js | 91 ++++++++++++++++++++++++++++++----- 5 files changed, 275 insertions(+), 26 deletions(-) diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index 2daf10bac..15a6e87aa 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -988,9 +988,33 @@ module.exports = function (chai, _) { function assertInstanceOf (constructor, msg) { if (msg) flag(this, 'message', msg); + + var target = flag(this, 'object') + var validInstanceOfTarget = constructor === Object(constructor) && ( + typeof constructor === 'function' || + (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined' && + typeof constructor[Symbol.hasInstance] === 'function') + ); + + if (!validInstanceOfTarget) { + var constructorType = constructor === null ? 'null' : typeof constructor; + throw new Error('The instanceof assertion needs a constructor but ' + constructorType + ' was given.'); + } + + var isInstanceOf; + try { + isInstanceOf = target instanceof constructor + } catch (err) { + throw new Error(target + ' instanceof ' + constructor + ' failed: ' + err.message + '.') + } + var name = _.getName(constructor); + if (name === null) { + name = 'an unnamed constructor'; + } + this.assert( - flag(this, 'object') instanceof constructor + isInstanceOf , 'expected #{this} to be an instance of ' + name , 'expected #{this} to not be an instance of ' + name ); diff --git a/package.json b/package.json index 0f9819d51..ce2fe78ae 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "assertion-error": "^1.0.1", "check-error": "^1.0.1", "deep-eql": "^2.0.1", - "get-func-name": "^1.0.0", + "get-func-name": "^2.0.0", "pathval": "^1.0.0", "type-detect": "^4.0.0" }, diff --git a/test/assert.js b/test/assert.js index d8d3db01a..d372fbc7b 100644 --- a/test/assert.js +++ b/test/assert.js @@ -141,6 +141,60 @@ describe('assert', function () { function Foo(){} assert.instanceOf(new Foo(), Foo); + err(function(){ + assert.instanceOf(new Foo(), 1); + }, "The instanceof assertion needs a constructor but number was given."); + + err(function(){ + assert.instanceOf(new Foo(), 'batman'); + }, "The instanceof assertion needs a constructor but string was given."); + + err(function(){ + assert.instanceOf(new Foo(), {}); + }, "The instanceof assertion needs a constructor but object was given."); + + err(function(){ + assert.instanceOf(new Foo(), true); + }, "The instanceof assertion needs a constructor but boolean was given."); + + err(function(){ + assert.instanceOf(new Foo(), null); + }, "The instanceof assertion needs a constructor but null was given."); + + err(function(){ + assert.instanceOf(new Foo(), undefined); + }, "The instanceof assertion needs a constructor but undefined was given."); + + var expectedError; + try { + t instanceof Thing; + } catch (err) { + errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.'; + } + + err(function(){ + function Thing(){}; + var t = new Thing(); + Thing.prototype = 1337; + assert.instanceOf(t, Thing); + }, expectedError); + + if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { + err(function(){ + assert.instanceOf(new Foo(), Symbol()); + }, "The instanceof assertion needs a constructor but symbol was given."); + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 3; + }; + + assert.instanceOf(fakeInstanceB, FakeConstructor); + }, 'expected 4 to be an instance of an unnamed constructor') + } + err(function () { assert.instanceOf(5, Foo); }, "expected 5 to be an instance of Foo"); @@ -156,6 +210,47 @@ describe('assert', function () { function Foo(){} assert.notInstanceOf(new Foo(), String); + err(function(){ + assert.notInstanceOf(new Foo(), 1); + }, "The instanceof assertion needs a constructor but number was given."); + + err(function(){ + assert.notInstanceOf(new Foo(), 'batman'); + }, "The instanceof assertion needs a constructor but string was given."); + + err(function(){ + assert.notInstanceOf(new Foo(), {}); + }, "The instanceof assertion needs a constructor but object was given."); + + err(function(){ + assert.notInstanceOf(new Foo(), true); + }, "The instanceof assertion needs a constructor but boolean was given."); + + err(function(){ + assert.notInstanceOf(new Foo(), null); + }, "The instanceof assertion needs a constructor but null was given."); + + err(function(){ + assert.notInstanceOf(new Foo(), undefined); + }, "The instanceof assertion needs a constructor but undefined was given."); + + + if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { + err(function(){ + assert.notInstanceOf(new Foo(), Symbol()); + }, "The instanceof assertion needs a constructor but symbol was given."); + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 4; + }; + + assert.notInstanceOf(fakeInstanceB, FakeConstructor); + }, 'expected 4 to not be an instance of an unnamed constructor'); + } + err(function () { assert.notInstanceOf(new Foo(), Foo); }, "expected {} to not be an instance of Foo"); diff --git a/test/expect.js b/test/expect.js index 1fecf02b7..e5b33212b 100644 --- a/test/expect.js +++ b/test/expect.js @@ -72,61 +72,61 @@ describe('expect', function () { expect(42).ok.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows overwritten property assertion', function () { err(function () { expect(42).tmpProperty.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled method assertion', function () { err(function () { expect(42).equal.pizza; }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".'); }); - + it('throws when invalid property follows called method assertion', function () { err(function () { expect(42).equal(42).pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled overwritten method assertion', function () { err(function () { expect(42).tmpMethod.pizza; }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".'); }); - + it('throws when invalid property follows called overwritten method assertion', function () { err(function () { expect(42).tmpMethod().pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled chainable method assertion', function () { err(function () { expect(42).a.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows called chainable method assertion', function () { err(function () { expect(42).a('number').pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled overwritten chainable method assertion', function () { err(function () { expect(42).tmpChainableMethod.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows called overwritten chainable method assertion', function () { err(function () { expect(42).tmpChainableMethod().pizza; }, 'Invalid Chai property: pizza'); }); - + it('doesn\'t throw if invalid property is excluded via config', function () { expect(function () { expect(42).then; @@ -366,6 +366,71 @@ describe('expect', function () { function Foo(){} expect(new Foo()).to.be.an.instanceof(Foo); + err(function(){ + expect(new Foo()).to.an.instanceof(1); + }, "The instanceof assertion needs a constructor but number was given."); + + err(function(){ + expect(new Foo()).to.an.instanceof('batman'); + }, "The instanceof assertion needs a constructor but string was given."); + + err(function(){ + expect(new Foo()).to.an.instanceof({}); + }, "The instanceof assertion needs a constructor but object was given."); + + err(function(){ + expect(new Foo()).to.an.instanceof(true); + }, "The instanceof assertion needs a constructor but boolean was given."); + + err(function(){ + expect(new Foo()).to.an.instanceof(null); + }, "The instanceof assertion needs a constructor but null was given."); + + err(function(){ + expect(new Foo()).to.an.instanceof(undefined); + }, "The instanceof assertion needs a constructor but undefined was given."); + + // Different browsers may have different error messages + var expectedError; + try { + t instanceof Thing; + } catch (err) { + errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.'; + } + + err(function(){ + function Thing(){}; + var t = new Thing(); + Thing.prototype = 1337; + expect(t).to.an.instanceof(Thing); + }, expectedError) + + if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { + err(function(){ + expect(new Foo()).to.an.instanceof(Symbol()); + }, "The instanceof assertion needs a constructor but symbol was given."); + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 3; + }; + + expect(fakeInstanceB).to.be.an.instanceof(FakeConstructor); + }, 'expected 4 to be an instance of an unnamed constructor') + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 4; + }; + + expect(fakeInstanceB).to.not.be.an.instanceof(FakeConstructor); + }, 'expected 4 to not be an instance of an unnamed constructor') + } + err(function(){ expect(3).to.an.instanceof(Foo, 'blah'); }, "blah: expected 3 to be an instance of Foo"); @@ -1826,7 +1891,7 @@ describe('expect', function () { expect(testSet).to.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']); expect(testSet).to.not.have.any.deep.keys([20, 1, {13: 37}]); expect(testSet).to.not.have.all.deep.keys([{thisIs: 'anExampleObject'}, {'iDoNot': 'exist'}]); - + var weirdSetKey1 = Object.create(null) , weirdSetKey2 = {toString: NaN} , weirdSetKey3 = [] diff --git a/test/should.js b/test/should.js index 612f74e90..fad0657bb 100644 --- a/test/should.js +++ b/test/should.js @@ -51,79 +51,79 @@ describe('should', function() { describe('proxify', function () { if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined') return; - + it('throws when invalid property follows should', function () { err(function () { (42).should.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows language chain', function () { err(function () { (42).should.to.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows property assertion', function () { err(function () { (42).should.ok.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows overwritten property assertion', function () { err(function () { (42).should.tmpProperty.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled method assertion', function () { err(function () { (42).should.equal.pizza; }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".'); }); - + it('throws when invalid property follows called method assertion', function () { err(function () { (42).should.equal(42).pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled overwritten method assertion', function () { err(function () { (42).should.tmpMethod.pizza; }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".'); }); - + it('throws when invalid property follows called overwritten method assertion', function () { err(function () { (42).should.tmpMethod().pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled chainable method assertion', function () { err(function () { (42).should.a.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows called chainable method assertion', function () { err(function () { (42).should.a('number').pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows uncalled overwritten chainable method assertion', function () { err(function () { (42).should.tmpChainableMethod.pizza; }, 'Invalid Chai property: pizza'); }); - + it('throws when invalid property follows called overwritten chainable method assertion', function () { err(function () { (42).should.tmpChainableMethod().pizza; }, 'Invalid Chai property: pizza'); }); - + it('doesn\'t throw if invalid property is excluded via config', function () { (function () { (42).should.then; @@ -416,6 +416,71 @@ describe('should', function() { function Foo(){} new Foo().should.be.an.instanceof(Foo); + err(function(){ + new Foo().should.be.an.instanceof(1); + }, "The instanceof assertion needs a constructor but number was given."); + + err(function(){ + new Foo().should.be.an.instanceof('batman'); + }, "The instanceof assertion needs a constructor but string was given."); + + err(function(){ + new Foo().should.be.an.instanceof({}); + }, "The instanceof assertion needs a constructor but object was given."); + + err(function(){ + new Foo().should.be.an.instanceof(true); + }, "The instanceof assertion needs a constructor but boolean was given."); + + err(function(){ + new Foo().should.be.an.instanceof(null); + }, "The instanceof assertion needs a constructor but null was given."); + + err(function(){ + new Foo().should.be.an.instanceof(undefined); + }, "The instanceof assertion needs a constructor but undefined was given."); + + // Different browsers may have different error messages + var expectedError; + try { + t instanceof Thing; + } catch (err) { + errMsg = '[object Object] instanceof function Thing(){} failed: ' + err.message + '.'; + } + + err(function(){ + function Thing(){}; + var t = new Thing(); + Thing.prototype = 1337; + t.should.be.an.instanceof(Thing); + }, expectedError); + + if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { + err(function(){ + new Foo().should.be.an.instanceof(Symbol()); + }, "The instanceof assertion needs a constructor but symbol was given."); + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 3; + }; + + fakeInstanceB.should.be.an.instanceof(FakeConstructor); + }, 'expected 4 to be an instance of an unnamed constructor'); + + err(function() { + var FakeConstructor = {}; + var fakeInstanceB = 4; + FakeConstructor[Symbol.hasInstance] = function (val) { + return val === 4; + }; + + fakeInstanceB.should.not.be.an.instanceof(FakeConstructor); + }, 'expected 4 to not be an instance of an unnamed constructor'); + } + err(function(){ (3).should.an.instanceof(Foo, 'blah'); }, "blah: expected 3 to be an instance of Foo");