Merge pull request #721 from meeber/property-validation

Throw when non-existent property is read
This commit is contained in:
Lucas Fernandes da Costa 2016-06-24 11:12:02 -03:00 committed by GitHub
commit 3ef86fdbf6
8 changed files with 133 additions and 4 deletions

View file

@ -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', {

View file

@ -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
);
});
@ -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
);
});
@ -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
);
}

View file

@ -10,6 +10,7 @@
var transferFlags = require('./transferFlags');
var flag = require('./flag');
var proxify = require('./proxify');
/*!
* Module variables
@ -104,7 +105,7 @@ module.exports = function (ctx, name, method, chainingBehavior) {
}
transferFlags(this, assert);
return assert;
return proxify(assert);
}
, configurable: true
});

View file

@ -152,3 +152,9 @@ exports.getOwnEnumerableProperties = require('./getOwnEnumerableProperties');
*/
exports.checkError = require('check-error');
/*!
* Proxify util
*/
exports.proxify = require('./proxify');

35
lib/chai/utils/proxify.js Normal file
View file

@ -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];
}
});
};

View file

@ -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

View file

@ -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

View file

@ -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();
});
});
});