-
-
Notifications
You must be signed in to change notification settings - Fork 700
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
Allow dates for isBelow and isAbove assertions #990
Conversation
I have probably made a newbie mistake deleting |
lib/chai/core/assertions.js
Outdated
|
||
if (doLength) { | ||
new Assertion(obj, msg).to.have.property('length'); | ||
} else { | ||
new Assertion(obj, msg).is.a('number'); | ||
// Since utils/expectTypes is missing from 4.x.x and no better way to assert multiple types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not find a better way to assert multiple types, on the original PR expectTypes
was suggested, but it's no longer present in the utils
lib/chai/core/assertions.js
Outdated
@@ -587,6 +606,7 @@ module.exports = function (chai, _) { | |||
* @api public | |||
*/ | |||
|
|||
// Should this also be updated? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally we discussed only isAbove
and isBelow
methods to support dates, but this seems connected as well. Same question goes for assertMost
and within
.
test/assert.js
Outdated
|
||
err(function() { | ||
assert.isAbove(oneSecondAgo, now); | ||
}, 'expected ' + now.toUTCString() + ' to be above ' + oneSecondAgo.toString()); // TODO: Fix different date formatting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I noticed is that date formatting is not uniform. While the first argument gets printed inside the error as a UTC string, second one is not. Probably should not be this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is created by util.getMessage
inside the Assertion.prototype.assert
here.
The util.getMessage
uses util.objectDisplay which ends up calling util.inspect that for Dates will call toUTCString() 😄
But the .above | .below
assertions are just concatenating the expected value to the message which just calls .toString()
.
In order to fix that you need to update the call to this.assert
.
Unfortunatelly github does not allow me to comment on unchanged lines so I'll try to explain here.
Basically you need to update this:
this.assert(
obj > n
, 'expected #{this} to be above ' + n
, 'expected #{this} to be at most ' + n
);
To this:
this.assert(
obj > n
, 'expected #{this} to be above #{exp}'
, 'expected #{this} to be at most #{exp}'
, n
);
I believe you will need to do the same thing for the .below assertion too
Please let me know if you have any further questions 😄
@v1adko thanks for this! One general note, before I review this; could you please change the base branch to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @v1adko, thank you for this PR!
I was thinking if we should actually call these isBefore and isAfter to make it more semantic.
Maybe an alias would be enough, but I'd prefer separing those two into separate assertions.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @v1adko, thank you so much for putting your time into this.
I think that implementing this as isBefore
and isAfter
looks cleaner and simpler.
The "assert multiple types" problem goes away too.
@lucasfcosta @vieiralucas #980 contains a discussion regarding overloading |
@meeber Having the target and given value must both be the same type looks enough to me |
lib/chai/core/assertions.js
Outdated
? 'number' | ||
: (objType === 'date') | ||
? 'date' | ||
: 'number or date'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, also maybe you could extract this code into a function to avoid duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where would be a better place for a function like this? Utils?
lib/chai/core/assertions.js
Outdated
? 'number' | ||
: (objType === 'date') | ||
? 'date' | ||
: 'number or date'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather use if | else if | else
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, no problem :) I know that there are plans to introduce a linter, would definitely be beneficial for such cases, no more "nits".
Also, you guys can take a look at prettier as an alternative to standard linter.
test/assert.js
Outdated
|
||
err(function() { | ||
assert.isAbove(oneSecondAgo, now); | ||
}, 'expected ' + now.toUTCString() + ' to be above ' + oneSecondAgo.toString()); // TODO: Fix different date formatting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is created by util.getMessage
inside the Assertion.prototype.assert
here.
The util.getMessage
uses util.objectDisplay which ends up calling util.inspect that for Dates will call toUTCString() 😄
But the .above | .below
assertions are just concatenating the expected value to the message which just calls .toString()
.
In order to fix that you need to update the call to this.assert
.
Unfortunatelly github does not allow me to comment on unchanged lines so I'll try to explain here.
Basically you need to update this:
this.assert(
obj > n
, 'expected #{this} to be above ' + n
, 'expected #{this} to be at most ' + n
);
To this:
this.assert(
obj > n
, 'expected #{this} to be above #{exp}'
, 'expected #{this} to be at most #{exp}'
, n
);
I believe you will need to do the same thing for the .below assertion too
Please let me know if you have any further questions 😄
lib/chai/core/assertions.js
Outdated
@@ -1151,6 +1183,7 @@ module.exports = function (chai, _) { | |||
* @api public | |||
*/ | |||
|
|||
// Should this also be updated? + assertMost and within |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally we discussed only isAbove
and isBelow
methods to support dates, but assertLeast
seems connected as well. Same question goes for assertMost
and within
. [restored]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup all of those should be updated as well.
lib/chai/core/assertions.js
Outdated
if (typeof n !== 'number') { | ||
flagMsg = flagMsg ? flagMsg + ': ' : ''; | ||
if (!(nType === 'number' || nType === 'date')) { | ||
var shoudBeType = (objType === 'number' || objType === 'date' ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vieiralucas Instead of doing else-if
I got rid of the nested ternary. If you think that it contradicts a style or obscures code, let me now and I'll change this too.
Restoring the question - if I were to separate this logic elsewhere, what's the best place? Utils?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, these functions (isAbove and isBelow) are very similar, apart from several strings and conditions, so if you really want to make it DRY, these can be separated into a some kind of assertion constructor that would take a predicate and some other params (like the string 'below/above').
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also know that there are plans to introduce a linter, would definitely be beneficial for such cases, no more "nits".
Also, you guys can take a look at prettier as an alternative to standard linter. [restored]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this type of utility function could just be local to the assertions file.
I wouldn't be opposed to a refactoring effort to further reduce duplication among these similar functions, as long as it didn't have a big impact on readability. That'd be a discussion for a separate issue/PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After also updating atMost, atLeast and within there is a definite pattern and code duplication, not only in these lines. So, I'm not sure what parts exactly to separate out, I think it's mostly the same code, just different conditions, so easier would be to create a generic assertion and "plug in" the differences.
test/expect.js
Outdated
// Is this right? Fails with the 'implementation frames error' (same as the 4 original ones in master) | ||
err(function () { | ||
expect(null).to.have.length.above(0, 'blah'); | ||
}, "Cannot read property 'length' of null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test fails, along with 4 tests concerning instance checks (they fail locally on clean master
too)
Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The .property
assertion is ungracefully failing on this line when the target is null
. This causes Chai's implementation frames to not be properly removed from the stack trace, which in turn causes this test using the err
helper function to fail. I guess this problem needs a separate issue.
@keithamus Changed to the master base. |
@v1adko Thanks for your work on this! A |
lib/chai/core/assertions.js
Outdated
); | ||
} | ||
|
||
// Should it check if before <= after? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that within method does not check weather the two given params make a valid range. Should it? Or it's assumed that it's gonna always throw and the user would see it from the generic error message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave it as-is for now. Can always readdress later.
lib/chai/utils/expectTypes.js
Outdated
@@ -30,10 +30,11 @@ module.exports = function expectTypes(obj, types, ssfi) { | |||
flagMsg = flagMsg ? flagMsg + ': ' : ''; | |||
|
|||
obj = flag(obj, 'object'); | |||
// TODO: Fix - results in undefined when passed date |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if obj
is a date, after a flag it returns undefined, do we care?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks to me like expectTypes
can only be used on assertion objects in order to make sure the target of the assertion (i.e., the value stored in the object
flag) is one of the allowed types. It can't be used to check that arguments passed to an assertion are of the correct type; the object
flag would be undefined
regardless of the argument type. So you're doing the correct thing by checking the argument type manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, non-issue then, got confused about the use-case of this method.
test/assert.js
Outdated
}, 'blah: expected ' + now.toUTCString() + ' to be at least ' + oneSecondAfter.toUTCString()); | ||
|
||
// These fail with 'implementation frames not properly filtered' | ||
err(function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly I still don't know how to approach this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed in #992
test/assert.js
Outdated
}, 'blah: type mismatch, expected to most value to be a number'); | ||
}); | ||
|
||
// There's no assert.isWithin(n, a, b)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While should an expect implement have this options, it looks like there's no simple assert.isWithin()
Should we add an alias?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say leave that for a separate PR.
@meeber Updated the other methods + tests too. And checkout chai worked, thanks 👍 Also I'm going to be out of town for the weekend without a computer, so, unfortunately, won't be able to work on this. Will gladly pick it up next week. |
@meeber All right, I'll wait till then |
If we could get this in a patch update ASAP that'd be great since this fixes an unnecessary breaking change (#980) from 3.x |
@meeber, @vieiralucas Hi :) Don't want to rush you guys or anything, just interested in how those PRs are moving along because they look green and it's already been a week. As I understood I need to wait for them to get merged first. |
I think I could live with that refactor 😄. Hopefully #585 will eventually get us to a point where this kind of manual type checking isn't necessary any more. |
test/expect.js
Outdated
}, "blah: expected [ 1, 2, 3 ] to have a length within 5..7"); | ||
|
||
err(function () { | ||
expect(null).to.be.within(0, 1, 'blah'); | ||
}, "blah: expected null to be a number"); | ||
}, "blah: the arguments to within must be numbers or dates"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@keithamus This doesn't seem right to me. Technically here both arguments to within are numbers, argument to expect isn't so the message is not very accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this message is a little vague. Can we add an extra if
to just throw saying something like "blah: expected null to be a number or date"
@keithamus Updated all methods. I also feel like L1082-L1094 can be extracted into a separate function (like UPD: I've managed to take out all of the common logic into one generic function, please take a look here. |
Thanks @v1adko. While the simplicity of creating assertions using the helper is definitely somewhere we want to be in future, I think for now having the repetitive yet easily understandable code in the PR as it stands now works well. In addition, #585 grants us the scope for these kinds of optimisations, where we can truly focus on getting chai assertions to be very easy to write. Let's just fix up the small issue you mentioned above, then get this PR merged! |
Perfect! Thanks @v1adko this looks good to me! @meeber, @lucasfcosta @vieiralucas as you've commented, do you all want to approve before we move this to merge? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Awesome work @v1adko, thank you ❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@v1adko Thanks again for your diligence on this one. I like this a lot, and it should be an easy transition to refactoring a bit later on in a separate PR. I do have a few nitpicks that I'd like to see cleaned up before we merge this.
Also, let's make sure all of these commits get rebased into a single one (either manually or via github) before merging.
lib/chai/core/assertions.js
Outdated
@@ -1024,7 +1024,7 @@ module.exports = function (chai, _) { | |||
/** | |||
* ### .above(n[, msg]) | |||
* | |||
* Asserts that the target is a number greater than the given number `n`. | |||
* Asserts that the target is number or a date greater than the given number or a date `n` respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't read exactly right to me. I think: "Asserts that the target is a number or a date greater than the given number or date n
respectively."
lib/chai/core/assertions.js
Outdated
* Asserts that the target is a number greater than or equal to the given | ||
* number `n`. However, it's often best to assert that the target is equal to | ||
* Asserts that the target is a number or a date greater than or equal to the given | ||
* number or a date `n` respectively. However, it's often best to assert that the target is equal to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't read exactly right to me. I think: "Asserts that the target is a number or a date greater than or equal to the given number or date n
respectively."
lib/chai/core/assertions.js
Outdated
@@ -1197,7 +1215,7 @@ module.exports = function (chai, _) { | |||
/** | |||
* ### .below(n[, msg]) | |||
* | |||
* Asserts that the target is a number less than the given number `n`. | |||
* Asserts that the target is a number or a date less than the given number or a date `n` respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't read exactly right to me. I think: "Asserts that the target is a number or a date less than the given number or date n
respectively."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meeber I don't see how what you wrote is different from what's already there (green).
Or is it supposed to be the same? Sorry, I'm a bit confused. What's would be the suggestions to make it read better?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@v1adko In almost every case, the suggestion is "given number or date" instead of "given number or a date". In addition to that, the one for .above
is also missing an "a" at the start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meeber So no articles then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Articles for the first half (either "the target is a number or a date", or, "the target is a number or date"). But no articles for the second half ("than the given number or date").
lib/chai/core/assertions.js
Outdated
* Asserts that the target is a number less than or equal to the given number | ||
* `n`. However, it's often best to assert that the target is equal to its | ||
* Asserts that the target is a number or a date less than or equal to the given number | ||
* or a date `n` respectively. However, it's often best to assert that the target is equal to its |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't read exactly right to me. I think: "Asserts that the target is a number or a date less than or equal to the given number or date n
respectively."
lib/chai/core/assertions.js
Outdated
* Asserts that the target is a number greater than or equal to the given | ||
* number `start`, and less than or equal to the given number `finish`. | ||
* Asserts that the target is a number or a date greater than or equal to the given | ||
* number or a date `start`, and less than or equal to the given number or a date `finish` respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't read exactly right to me. I think: "Asserts that the target is a number or a date greater than or equal to the given number or date start
, and less than or equal to the given number or date finish
respectively."
assert.isBelow(1, now, 'blah'); | ||
}, 'blah: the argument to below must be a number'); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add the custom message (blah
) testing to the other failure cases here.
test/should.js
Outdated
(now).should.not.be.within(now, null, 'blah'); | ||
}, "blah: the arguments to within must be dates"); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need parenthesis around <var name>
whenever it's (<var name>).should
; still need parenthesis for the values plugged directly, like (0).should
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, if I understood correctly, just omit parenthesis and do now.should
instead of (now).should
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup!
([]).should.have.length.above(now, 'blah'); | ||
}, "blah: the argument to above must be a number"); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need parenthesis around <var name>
whenever it's (<var name>).should
; still need parenthesis for the values plugged directly, like (0).should
('string').should.have.length.below(now, 'blah'); | ||
}, "blah: the argument to below must be a number"); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need parenthesis around <var name>
whenever it's (<var name>).should
; still need parenthesis for the values plugged directly, like (0).should
test/should.js
Outdated
(now).should.not.be.at.most(undefined); | ||
}, "the argument to most must be a date"); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need parenthesis around <var name>
whenever it's (<var name>).should
; still need parenthesis for the values plugged directly, like (0).should
@meeber Done. As for the commits - I think it's better to squash them via GitHub. |
@v1adko The tests for |
@meeber Missed that, thanks :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! (To whomever merges: remember to squash into single commit!)
🎉 ✨ |
Awesome! Can we get a release for this? Thanks. |
@cletusw Let's see what other people think, but IMO we should. In order to avoid having to wait for too long for a release like we did last time I think it would be awesome for us to keep releasing new versions with What do you think? |
Eventually we'll hopefully end up adding semantic-release to this repo like the others; in the meantime anyone is welcome to raise a PR of the output of |
Hi guys.
This closes #980. I took #692 as an "inspiration" and extended the code to support dates as discussed in the issue.
I have encountered some questions along the way and left comments (off course will delete them once we decided what to do at those moments) - I will expand on them within GitHub, to make them easier to notice. So it's a WIP for now, just need a bit of your guidance.
This is my first attempt at actually PR-ing anything open-source, so sorry for mistakes.
I'd really like to contribute :)
Also need to fix up Phantom tests, but wanted to get some feedback and ask these questions first, to understand the general direction and the scope of this PR better.