Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw when non-existent property is read #721

Merged
merged 1 commit into from
Jun 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/chai/assertion.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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();
});
});
});