-
-
Notifications
You must be signed in to change notification settings - Fork 258
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
setupOnError not working #1128
Comments
@amk221 Looks like this is a known issue, there is some discussion in the latter part of this thread in the |
I got fed up with this long-standing issue, so I decided to invest a bit of time and do some detective work. 🕵️😄 This occurs, when an error is thrown outside of the Ember run loop. The error then propagates to the global It still shows up as a global failure in your QUnit test, because QUnit catches these via QUnit.onUncaughtException, like @snewcomer correctly pointed out in the issue that you linked: emberjs/ember-qunit#592 (comment) Running outside of a run loop can easily happen by accident, for instance when you manually add an event listener to an element and don't wrap the listener in import { run } from '@ember/runloop';
element.addEventListener('click', () => {
run(() => {
throw Error('caught by `setupOnerror`');
});
});
element.addEventListener('click', () => {
throw Error('NOT caught by `setupOnerror`');
}) And in fact, if you change your import Component from '@glimmer/component';
import { action } from '@ember/object';
import { run } from '@ember/runloop';
export default class MyComponent extends Component {
@action
check() {
run(() => {
throw new Error('check failed');
});
}
} Now the Ember Guides claim that
This would lead me to believe that using Ember transparently uses Glimmer's I would consider this is a bug, because it violates the promise made in the Ember Guides that you don't need to concern yourself with This only really becomes an issue in tests, because But the trouble doesn't stop there. Regular "native" DOM events initiated by a user action / the browser are handled asynchronously as opposed to synthetic events, i.e. events triggered manually via
try {
await click('.some-element-that-throws-on-click');
} catch (error) {
console.log(error); // This should log the error from the click handler.
} Alas, that also doesn't work: #310, #453, #440 But why? Even though Ideally though, the handler would be wrapped in a run loop, so that |
What's the situation with this? It's happening more and more. eslint-plugin-ember recently added a We were using |
Don't use runloop to 'get around' this. You can use try/catch for exceptions from click events, or assert.throws. :) <3 setupOnerror is only for runloop stuff, which is why putting stuff in runloop queues makes errors show up there. It's a separate discussion to have setupOnerror also listen to window error events (where errors during click would end up). |
How are we supposed to test error scenarios (unhappy paths) in acceptance tests though? They cause the tests to fail, which is why we use |
Can you provide a minimal repro of your specific example? It's totally valid atm to use setupOnerror for rendering errors and assert.throws for others, atd try/catch for others - which should cover your bases) |
Sure, amk221/-ember-unhappy-path-test@2797b67 It's related to this emberjs/ember-qunit#592 as well |
there are no failing tests on the |
It’s commented as to why |
gotchya, well, I discovered some things, so thank you <3 It seems this is related to a limitation in The Platform for certain styles of throwing errors. I made a demo here to demonstrate (and to remove all possible ember/qunit suspicions) https://jsbin.com/jakecim/edit?html,output In particular, when catching errors (event listening, etc), "uncaught (in promise)" errors always reach the "root" / console / browser, and it doesn't seem there is a way to "stopPropagation", as you would with click event handlers. This seems to back up the experience of others as well:
So, in the JSBin demo, I've added a checkbox (enabled by default) which makes the click-handler async/await (so that the click handler does not lose track of the async stack), and then all 3 scenarios (async, promise chain, and sync), are correctly caught and don't bubble up to the window -- unchecking this checkboxe reproduces the behavior you're experiencing in your app -- I believe this solution is in alignment with what others have discovered as well (the stackoverflow, for example). I do see a bit of varying behavior between Chrome and Firefox though (which is in alignment with the stackoverflow post as well). In particular, Chrome doesn't seem to "reduntantly" print the error in the the jsbin demo. So it's possible that this JSBin has the solution ya'll are looking for, if you only want to test against Chrome(based) browsers. that solution is probably this: window.addEventListener('unhandledrejection', (event) => {
console.info('unhandledrejection handler called');
event.preventDefault()
}); specifically, I'm going to close this issue, as I don't think there is anything better we can do with If someone can come up with some way "preventDefault" / "stopPropagation" on error-event handlers, at some point in the future, let's open up a new issue and discuss there (or better, a PR! ✨✨✨ ) -- as I do agree with ya'll that it'd be great to have some unified way to test/assert/capture errors in tests. |
Thanks for the investigation. Where does that leave us though? It's impossible to test certain things, consider: export default class extends Component {
constructor() {
super(...arguments);
assert('@foo must be provided', 'foo' in this.args);
}
} test('foo', async function(assert) {
assert.expect(1);
setupOnerror((error) => {
if (
error.message === 'Assertion Failed: @foo must be provided'
) {
assert.step('ensures correct usage');
return;
}
throw error;
});
await render(hbs`<MyComponent />`);
assert.verifySteps(['ensures correct usage']);
}); |
@amk221 I wonder about a test like that though 🤔 the implementation of I understand that this could just be used for an illustration but the reason I am asking is because it could be that you should be testing at a different layer rather than trying to "catch" exceptions thrown in constructors of components 🤔 |
Yeh, I know they are stripped, it's to help guide developers. But yes, for example purposes only. There are still scenarios that are untestable. |
This isn't true -- #1128 (comment) provided ways to capture all sorts of errors, exceptions, from anywhere. Now, if you mean with |
🤔 I must be missing something glaringly obvious.... Because nothing I've seen can get the acceptance tests to pass once the error reaches the window. Neither the tools from I'm happy to wash my hands of |
Made an example: amk221/-ember-unhappy-path-test#1 There is another example of how to do this here: qunitjs/qunit#1419 (comment) -- albeit, not contextually ergonomic (nor does it actually work -- as QUnit's properties are readonly (only So, I've opened this issue on the QUnit repo, where I think we should figure out some solution for QUnit v3. qunitjs/qunit#1736 Maybe it's qunit that needs to provide setupOnError / resetOnError (or similar) for handling this situation. |
Here are two test cases.
One where a component errors in constructor, and another where it errors in response to a click.
The first can be caught by
setupOnError
, and ignored in the test suite. 👍🏻The second is not caught by
setupOnError
, and so the test fails. 👎🏻The text was updated successfully, but these errors were encountered: