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

property assertion should only accept strings if nested, fixes #1043 #1044

Merged
merged 10 commits into from Oct 2, 2017
17 changes: 14 additions & 3 deletions lib/chai/core/assertions.js
Expand Up @@ -1763,10 +1763,22 @@ module.exports = function (chai, _) {
, isOwn = flag(this, 'own')
, flagMsg = flag(this, 'message')
, obj = flag(this, 'object')
, ssfi = flag(this, 'ssfi');
, ssfi = flag(this, 'ssfi')
, nameType = typeof name;

flagMsg = flagMsg ? flagMsg + ': ' : '';

if (isNested) {
if (nameType !== 'string') {
throw new AssertionError(flagMsg + 'the argument to `property` must be a string');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about telling that it must be string because the nested flag is on?

Something like:

throw new AssertionError(flagMsg + 'the argument to `property` with nested flag must be a string');

I'm afraid that it will be confusing to sometimes say that it can be a string, number or symbol and sometimes just saying that it must be a string

Thoughts? @keithamus @solodynamo @meeber

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But rather than writing nested flag we should think something else as end user may not know about internal logic of isNested. Suggestions ? @vieiralucas @meeber @keithamus

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, we can safely assume to get here they've written .nested.propery() so we can use that instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user could reach this error via the assert interface too, though, so nested.property wouldn't always be entirely accurate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time thinking on a good message

the argument to `property` when nesting must be a `string`

the argument to `property` for a nested assertion must be a `string`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

Normal: the argument to property must be a string, number, or symbol
Nested: the argument to property must be a string when using nested syntax

}
} else {
if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') {
throw new AssertionError(flagMsg + 'the argument to `property` must be either of type string, number or symbol');
}
}

if (isNested && isOwn) {
flagMsg = flagMsg ? flagMsg + ': ' : '';
throw new AssertionError(
flagMsg + 'The "nested" and "own" flags cannot be combined.',
undefined,
Expand All @@ -1775,7 +1787,6 @@ module.exports = function (chai, _) {
}

if (obj === null || obj === undefined) {
flagMsg = flagMsg ? flagMsg + ': ' : '';
throw new AssertionError(
flagMsg + 'Target cannot be null or undefined.',
undefined,
Expand Down
14 changes: 14 additions & 0 deletions test/assert.js
@@ -1,5 +1,6 @@
describe('assert', function () {
var assert = chai.assert;
var expect = chai.expect;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not add expect to the assert interface tests. It should be possible to test errors without expect by using the same style that's used elsewhere in this file.


it('assert', function () {
var foo = 'bar';
Expand Down Expand Up @@ -1395,6 +1396,7 @@ describe('assert', function () {
var obj = { foo: { bar: 'baz' } };
var simpleObj = { foo: 'bar' };
var undefinedKeyObj = { foo: undefined };
var dummyObj = { a: '1' };
assert.property(obj, 'foo');
assert.property(obj, 'toString');
assert.propertyVal(obj, 'toString', Object.prototype.toString);
Expand Down Expand Up @@ -1454,6 +1456,18 @@ describe('assert', function () {
err(function () {
assert.property(undefined, 'a', 'blah');
}, "blah: Target cannot be null or undefined.");

err(function () {
assert.propertyVal(dummyObj, 'a', '2', 'blah');
}, "blah: expected { a: '1' } to have property 'a' of '2', but got '1'");

expect(function () {
assert.nestedProperty({a:1}, '{a:1}');
}).to.not.throw('the argument to `property` must be a string');

expect(function () {
assert.nestedProperty({a:1}, {'a':'1'});
}).to.throw('the argument to `property` must be a string');
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are using chai.expect on line 18 too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's shouldn't be wrong to use expect in this test, because here we are actually using chai to assert, we are not calling assertions and expecting them to work, we are testing that the assertion throws.

But I agree with @meeber that we must try and be able to use own interface to test it.

You can replace the ones you added on this PR with assert.throws and assert.doesNotThrows and maybe remove the others in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure @vieiralucas . I have replaced them and also surely will open a seperate PR in future to replace the remaining.

it('deepPropertyVal', function () {
Expand Down
8 changes: 8 additions & 0 deletions test/expect.js
Expand Up @@ -1462,6 +1462,10 @@ describe('expect', function () {
err(function () {
expect(undefined, 'blah').to.have.property("a");
}, "blah: Target cannot be null or undefined.");

expect(function () {
expect({a:1}).to.have.property(null);
}).to.throw('the argument to `property` must be either of type string, number or symbol');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's repeat these three new error tests in the should and assert interface test files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay , so just duplicating them in both the files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithamus @meeber isNested is true when name is undefined , Shouldn't this be false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@solodynamo I'm not sure what you mean.

On a separate note, I just noticed a couple of things:

  • Let's remove the .not.throw test on lines 1466-1468; we already test successful .nested.property assertions in the section that starts on line 1754.
  • Let's relocate the test on lines 1470-1472 to the end of the nested.property section (that starts on line 1754).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meeber I meant to ask
expect(function () { ({a:1}).should.have.nested.property(undefined); }).to.throw('the argument toproperty must be either of type string, number or symbol');
Above test is throwing error 'the argument to property must be a string'. I debugged and found isNested is true in above case , so i meant isNested should be false ? Please correct me if i misunderstood something.

});

it('property(name, val)', function(){
Expand Down Expand Up @@ -1755,6 +1759,10 @@ describe('expect', function () {
expect({ 'foo.bar': 'baz' })
.to.have.nested.property('foo.bar');
}, "expected { 'foo.bar': 'baz' } to have nested property 'foo.bar'");

expect(function () {
expect({a:1}).to.have.nested.property({'a':'1'});
}).to.throw('the argument to `property` must be a string');
});

it('nested.property(name, val)', function(){
Expand Down
11 changes: 11 additions & 0 deletions test/should.js
@@ -1,5 +1,6 @@
describe('should', function() {
var should = chai.Should();
var expect = chai.expect;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here. Let's not add expect to the should interface tests.


it('assertion', function(){
'test'.should.be.a('string');
Expand Down Expand Up @@ -1407,6 +1408,16 @@ describe('should', function() {

({ 'foo.bar[]': 'baz'}).should.have.nested.property('foo\\.bar\\[\\]');

({a:1}).should.have.nested.property('a');

expect(function () {
({a:1}).should.have.nested.property('{a:1}');
}).to.not.throw('the argument to `property` must be a string');

expect(function () {
({a:1}).should.have.nested.property({'a':'1'});
}).to.throw('the argument to `property` must be a string');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more test should be added here to the should interface tests in order to verify the error message "the argument to property must be a string, number, or symbol". This test can closely resemble the expect({a:1}).to.have.property(null); test that you added on the expect interface, except using the should interface this time instead of expect. (And using the err helper function).

err(function(){
({ 'foo.bar': 'baz' }).should.have.nested.property('foo.bar');
}, "expected { 'foo.bar': 'baz' } to have nested property 'foo.bar'");
Expand Down