Skip to content

Commit

Permalink
Merge pull request #721 from meeber/property-validation
Browse files Browse the repository at this point in the history
Throw when non-existent property is read
  • Loading branch information
lucasfcosta committed Jun 24, 2016
2 parents 9dcd0eb + d0972d2 commit 3ef86fd
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 4 deletions.
2 changes: 2 additions & 0 deletions lib/chai/assertion.js
Expand Up @@ -33,6 +33,8 @@ module.exports = function (_chai, util) {
flag(this, 'ssfi', stack || Assertion);
flag(this, 'object', obj);
flag(this, 'message', msg);

return util.proxify(this);
}

Object.defineProperty(Assertion, 'includeStack', {
Expand Down
6 changes: 3 additions & 3 deletions lib/chai/core/assertions.js
Expand Up @@ -300,7 +300,7 @@ module.exports = function (chai, _) {
true === flag(this, 'object')
, 'expected #{this} to be true'
, 'expected #{this} to be false'
, this.negate ? false : true
, flag(this, 'negate') ? false : true
);
});

Expand All @@ -322,7 +322,7 @@ module.exports = function (chai, _) {
false === flag(this, 'object')
, 'expected #{this} to be false'
, 'expected #{this} to be true'
, this.negate ? true : false
, flag(this, 'negate') ? true : false
);
});

Expand Down Expand Up @@ -1528,7 +1528,7 @@ module.exports = function (chai, _) {
result
, 'expected #{this} to satisfy ' + _.objDisplay(matcher)
, 'expected #{this} to not satisfy' + _.objDisplay(matcher)
, this.negate ? false : true
, flag(this, 'negate') ? false : true
, result
);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/chai/utils/addChainableMethod.js
Expand Up @@ -10,6 +10,7 @@

var transferFlags = require('./transferFlags');
var flag = require('./flag');
var proxify = require('./proxify');

/*!
* Module variables
Expand Down Expand Up @@ -104,7 +105,7 @@ module.exports = function (ctx, name, method, chainingBehavior) {
}

transferFlags(this, assert);
return assert;
return proxify(assert);
}
, configurable: true
});
Expand Down
6 changes: 6 additions & 0 deletions lib/chai/utils/index.js
Expand Up @@ -152,3 +152,9 @@ exports.getOwnEnumerableProperties = require('./getOwnEnumerableProperties');
*/

exports.checkError = require('check-error');

/*!
* Proxify util
*/

exports.proxify = require('./proxify');
35 changes: 35 additions & 0 deletions lib/chai/utils/proxify.js
@@ -0,0 +1,35 @@
/*!
* Chai - proxify utility
* Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/

/**
* # proxify(object)
*
* Return a proxy of given object that throws an error when a non-existent
* property is read. (If Proxy or Reflect is undefined, then return object
* without modification.)
*
* @param {Object} obj
* @namespace Utils
* @name proxify
*/

module.exports = function proxify (obj) {
if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined')
return obj;

return new Proxy(obj, {
get: function getProperty (target, property) {
// Don't throw error on Symbol properties such as Symbol.toStringTag, nor
// on .then because it's necessary for promise type-checking.
if (typeof property === 'string' &&
property !== 'then' &&
!Reflect.has(target, property))
throw Error('Invalid Chai property: ' + property);

return target[property];
}
});
};
25 changes: 25 additions & 0 deletions test/expect.js
Expand Up @@ -10,6 +10,31 @@ describe('expect', function () {
expect('foo').to.equal('foo');
});

it('invalid property', function () {
if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined') return;

err(function () {
expect(42).pizza;
}, 'Invalid Chai property: pizza');

err(function () {
expect(42).to.pizza;
}, 'Invalid Chai property: pizza');

err(function () {
expect(42).to.be.a.pizza;
}, 'Invalid Chai property: pizza');

err(function () {
expect(42).to.equal(42).pizza;
}, 'Invalid Chai property: pizza');

// .then is excluded from property validation for promise support
expect(function () {
expect(42).then;
}).to.not.throw();
});

it('no-op chains', function() {
function test(chain) {
// tests that chain exists
Expand Down
25 changes: 25 additions & 0 deletions test/should.js
Expand Up @@ -7,6 +7,31 @@ describe('should', function() {
should.not.equal('foo', 'bar');
});

it('invalid property', function () {
if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined') return;

err(function () {
(42).should.pizza;
}, 'Invalid Chai property: pizza');

err(function () {
(42).should.be.pizza;
}, 'Invalid Chai property: pizza');

err(function () {
(42).should.be.a.pizza;
}, 'Invalid Chai property: pizza');

err(function () {
(42).should.equal(42).pizza;
}, 'Invalid Chai property: pizza');

// .then is excluded from property validation for promise support
(function () {
(42).should.then;
}).should.not.throw();
});

it('no-op chains', function() {
function test(chain) {
// tests that chain exists
Expand Down
35 changes: 35 additions & 0 deletions test/utilities.js
Expand Up @@ -844,4 +844,39 @@ describe('utilities', function () {
expect(gettem(obj)).to.have.same.members([cat, dog, bird]);
});
});

describe('proxified object', function () {
if (typeof Proxy === 'undefined' || typeof Reflect === 'undefined') return;

var proxify;

beforeEach(function () {
chai.use(function (_chai, _) {
proxify = _.proxify;
});
});

it('returns property value if an existing property is read', function () {
var pizza = proxify({mushrooms: 42});

expect(pizza.mushrooms).to.equal(42);
});

it('throws error if a non-existent property is read', function () {
var pizza = proxify({});

expect(function () {
pizza.mushrooms;
}).to.throw('Invalid Chai property: mushrooms');
});

// .then is excluded from property validation for promise support
it('doesn\'t throw error if non-existent `then` is read', function () {
var pizza = proxify({});

expect(function () {
pizza.then;
}).to.not.throw();
});
});
});

0 comments on commit 3ef86fd

Please sign in to comment.