-
-
Notifications
You must be signed in to change notification settings - Fork 702
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
Fixes an issues when both .throws()
and not.throws()
produce failures.
#551
Conversation
Shoud I still maintain it ? |
Thanks for the PR @jails. It's a bit of a big one, so I'm going to stick it on my todo list for this weekend. Just sit tight and I'll definitely get to looking at it! |
👍 |
1 similar comment
👍 |
+1 |
1 similar comment
+1 |
} | ||
function similarMessage (expected, thrown) { | ||
var isThrownedException = typeof thrown === 'object' && thrown.hasOwnProperty('message'); | ||
var message = isThrownedException ? thrown.message : '' + thrown; |
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 prefer an explicit String()
conversion here, as if a class has valueOf()
it will be called over toString(), e.g.:
'' + { valueOf() { return 1; }, toString() { return 'hello'; } }
@jails as you can see above I've made some notes about the code. I really like the general direction you're going with this, I feel like the error checking could be made much simpler though. We can distill this down to two essential checks:
We also currently check for referential equality, although I really wonder how much utility that has... I'd happily release a breaking version where we drop referential equality checking, but I suppose it is an easy piece of code to shortcut the longhand logic and just say Anyway - my point is that I get the feeling the functions should match these checks. You're very close with what you have, but I think it could be more concise, following the above list |
Thanks for the feedback, I updated the PR with the following changes:
For the code duplication part I wasn't able to find a better option. So I'll leave it up to you since you know the util codebase better than me. Otherwise for the boths checks, I'm not sure to get what you meant since it's actually the implemented behavior. However I renamed So the constructor check is ignored when using a single regexp (or a string): The exception message is also ignored when a exception with no message is expected: Finally I wasn't able to find any alternative for the referential equality except having a BC break here. Hope it makes sense. |
Sorry @jails I should clarify better. What I mean to say about the checks, is that I feel like something like this approximation would be close to passing our requirements (and tests) function compatibleErrorConstructor (expected, thrown) {
if (typeof expected === 'undefined' || expected instanceof RegExp || typeof expected === 'string' || expected === thrown) {
return true;
} else if (typeof expected === 'function') {
return thrown instanceof expected;
} else if (typeof expected.constructor === 'function') {
return thrown instanceof expected.constructor;
}
return false;
}
function compatibleErrorMessage (expected, thrown) {
if (typeof expected === 'undefined' || typeof expected === 'function' || expected === thrown) {
return true;
}
var isThrownedException = typeof thrown === 'object' && thrown.hasOwnProperty('message');
var message = isThrownedException ? thrown.message : '' + String(thrown);
if (expected instanceof RegExp) {
return expected.test(message);
} else if (typeof expected === 'object' && expected.hasOwnProperty('message')) {
return expected.message === '' || message === expected.message;
}
return message.indexOf(expected) !== -1;
}
function assertThrows (constructor, errMsg, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
new Assertion(obj, msg).is.a('function');
var expectedString = ((constructor||{}).name || 'Error') + (errMsg ? ': ' + errMsg : '');
var thrown;
try {
obj();
} catch (err) {
thrown = err;
}
if (thrown === undefined) {
this.assert(
false
, 'expected #{this} to throw ' + expectedString
, 'expected #{this} to not throw ' + expectedString
);
return this;
}
var hasCompatibleConstructor = compatibleErrorConstructor(constructor, thrown);
var hasCompatibleMessage = compatibleErrorMessage(errMsg || constructor, thrown);
this.assert(
hasCompatibleConstructor && hasCompatibleMessage
, 'expected #{this} to throw error #{exp} but got #{act}'
, 'expected #{this} to not throw error #{exp}'
, expectedString
, String(thrown)
);
flag(this, 'object', thrown);
return this;
}; (Note how most of the detection logic is pulled into the |
Definitely 👍 ! It was just a proof of concept so feel free to use the implementation you prefer ! |
@jails sorry, that comment wasn't meant to discourage you from continuing the work on this PR - more as notes to improve it. I'd definitely like to merge this, but my aim was to steer you more into a direction like the above (incomplete) implementation. Please, if you would like to continue working on this PR, I'd really like it you to do so and I really do want to merge your work! Do you have any critical thoughts of the sketch I gave? Do you think you could work your PR into something like that? |
👍 Let's get thsi into |
I was going to address a shortcoming I find, but it also impacts this part of the code. Perhaps I can explain and have this issue addressed, or I can open a different issue. Otherwise, perhaps there is a better approach to handle what I am struggling with? The
The second assertion fails with:
There is code in place that seems to attempt to pull a message property from an Error type, but it seems a bit convoluted, and ends up producing the never-wanted |
Hey @oravecz - that is somewhat intentional. You shouldn't be throwing non-error objects due to the fact that they provide no stack trace information, which causes problems when debugging. You'll note that any error state within JavaScript or the DOM is always an error object. However if you feel strongly about it you should raise a separate issue and we can discuss it further there. |
Turns out there is a workaround...
It was unexpected to me that the exception object would be available to inspect after a |
+1 |
@keithamus' implementation seems adequate and shrugs some code (also making things more efficient). Are you still working on this @jails? Your PR seems great and I think it would be awesome to merge it if we took @keithamus considerations into account. Please let me know if you need anything, I would certainly be very happy to help you. |
@lucasfcosta 👍 💯 keep up the good work 😄 |
Hello guys, I've asked @jails on twitter if he's still working on this and unfortunately he said that he gave up on this. I also noticed that @brunops sent a PR (#635) related to this, so I feel like he could use this discussion and code as "inspiration" for his implementation. Would like to take it forward, @brunops? What are your thoughts on this, @keithamus ? |
@lucasfcosta hold off on this one just for now - I want to make a little lib for checking equality errors, as we have a few places it can be used and also other libs like chai-as-promised could use it. I'll get the lib done over the weekend and we can look to integrate it into the error matchers in Chai. |
But thanks for trying to get this one resolved, and reaching out to @jails 😄 |
@keithamus seems awesome! |
@keithamus I know I'm late to the game but I couldn't help but notice how similar this issue is to the Validate an Error is thrown of any type with any properties: expect(fn).to.throw(); Validate an Error is thrown that's an instance of the given type with any properties: expect(fn).to.throw(TypeError); Validate an Error is thrown that's referentially equal to the given Error: var err = new TypeError("Test");
expect(fn).to.throw(err); Validate an Error is thrown that's an instance of the given type with an exact message: expect(fn).to.throw(TypeError, "oops"); Validate an Error is thrown that's an instance of the given type with a matching message: expect(fn).to.throw(TypeError, /oops/); Validate an Error is thrown that's an instance of the given type with deeply equal properties: expect(fn).to.throw(TypeError, {message: "oops", stack: "Type Error: poo at blahblah"}); Validate an Error is thrown that's an instance of the given type with one or more matching properties: expect(fn).to.throw(TypeError, {message: "oops", stack: /Type Error: poo at blahblah/}); Validate an Error is thrown of any type with an exact message: expect(fn).to.throw("oops"); Validate an Error is thrown of any type with a matching message: expect(fn).to.throw(/oops/); Validate an Error is thrown of any type with deeply equal properties: expect(fn).to.throw({message: "oops", stack: "Type Error: doh at blahblah"}); Validate an Error is thrown of any type with one or more matching properties: expect(fn).to.throw({message: "oops", stack: /Type Error: doh at blahblah/}); The |
Hi @meeber, thanks for your feedback! Giving a single EDIT: Oh, I forgot this is already supported, as said by @meeber below, srry Currently I'm working to move Thanks again for sharing your ideas! 😄 |
@lucasfcosta Thanks for the reply! I can't take credit for the single argument var err = new ReferenceError('This is a bad function.');
var fn = function () { throw err; }
expect(fn).to.throw(ReferenceError);
expect(fn).to.throw(Error);
expect(fn).to.throw(/bad function/);
expect(fn).to.not.throw('good function');
expect(fn).to.throw(ReferenceError, /bad function/);
expect(fn).to.throw(err); You make a good point about fluidity. The current implementation is a bit convoluted with the argument overloading, and my suggestion exacerbates that further. By instead relying on chained assertions, you lose a bit of succinctness in the API, but you gain sweet simplicity and cleaner functions. Easier to test too. Hard to argue with that ;) I look forward to the new modular approach! |
@lucasfcosta do we need to do anything with this issue now we have #683 merged? |
@keithamus I think we're done with this one since the spec is now fully compliant with the behavior described in the first post, we even have tests similar to the ones on the first post of this one. Before closing this one I would also like to thank everyone here, especially @jails for contributing and sharing your thoughts on this matter. Great job everyone! |
Great work @lucasfcosta. Also @jails thanks so much for working on this, and thank you so much for your patience through all of this - even after it has ultimately not been merged. Please don't let us closing this dissuade you from making more awesome contributions like this! |
This PR solves an issue when both
.throws()
andnot.throws()
produce failures. Exemple:Imo the
not
spec above should pass.This PR solves this issue and also modify the initial behavior by perfoming lazy comparison on exceptions to allows this syntax to make it works out of the box:
Indeed previoulsy a
===
comparison was made on objects which requires the following syntax:Now both syntax can be used either way. the only difference is one is assuming the error message to be strictly identical since the second way only requires
testing
to be included in the error message.Solves #436, #430 and probaly #470