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

Error when interacting with disabled form controls #778

Merged
merged 1 commit into from
Apr 21, 2020

Conversation

ro0gr
Copy link
Contributor

@ro0gr ro0gr commented Mar 9, 2020

With this, any action helper(click, triggerEvent,...) would fail on attempt to be invoked against input, textarea, select in disabled state.

I've extracted it from #741, cause fillIn and typeIn seem to be a bit different cases, and it should be easier to review this way. Also this is possibly a breaking change, so we may want to postpone merging it, while fillIn and typeIn seem to be more critical to fix.

if (!isDisabledFormControl) {
__click__(element, options);
if (isFormControl(element) && element.disabled) {
throw new Error(`Can not \`click\` disabled ${element}`);
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 not sure if we should throw an error for this. A real user is absolutely able to click on a disabled element, the difference though is that nothing will/should happen because the element is disabled.

Copy link
Contributor Author

@ro0gr ro0gr Apr 17, 2020

Choose a reason for hiding this comment

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

The intention of the PR is to disallow any interactions over disabled form elements. It seems the most effective to achieve it via throwing in the focus. This throw statement in click has been put here just for a better error message. If remove it, it'd fail later in the __focus__ util anyways.

A real user is absolutely able to click on a disabled element

yes, I agree about the user perspective. However, test env seems to be a bit different. If we don't fail as fast as we can, tracking the source of invalid test results can be more difficult then it could be if we were more strict.
In tests, we usually have all the state under control, and before clicking smth, we, as test authors, should likely know a desirable state for the button.

For instance, if we have a button which is responsible for some computation, and we click it while it's disabled(because of some bug), we may have a failing assert for the expected computation value. And since we don't fail fast, the more distance between the action and assert is, the more difficult to track the source of the issue. I believe test helpers could become more helpful here, by letting the user know if he tries to interact w/ something which is not supposed to be interactable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are 2 possible reasons I can see, why someone clicks on a disabled element:

  • they intentionally did want to check if smth doesn't occur after click. In this case, the question is why not to just check for disabled value? And it also encourages the fail-late approach which I've tried to describe in the previous comment.
  • the button should not have been disabled, but disabled because of some code issue. In this case we fall again in a "fail late" thing. And there is even a probability to have a false-positive test results if some assert is missing.

Curious if there are any other use cases for clicking disabled elements in tests? 🤔

Copy link

Choose a reason for hiding this comment

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

I agree with @Turbo87 - it shouldn't throw, just do nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd be happy to agree as well. But don't see a good reason for that. The only one which is raised:

A real user is absolutely able to click on a disabled element, the difference though is that nothing will/should happen because the element is disabled.

however, in addition to my previous arguments, I don't find this aligned with some of the current behaviors. For instance, we currently fail on attempt to focus non-focusable elements

if (!isFocusable(element)) {
throw new Error(`${target} is not focusable`);
}

while in reality a user can absolutely try to focus on a disabled element or smth else w/o any success or exceptions in UI.

Or, we fail on attempt to fill-in non-editable elements

if (!isControl && !element.isContentEditable) {
throw new Error('`fillIn` is only usable on form controls or contenteditable elements.');
}

As far as I see, these all are just examples of fast feedback for the test helpers user, which allows restrict he/she from doing pointless things.

Copy link
Member

Choose a reason for hiding this comment

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

In general, I agree with @ro0gr here. The general purpose helpers we provide here are intended to be demonstrating the functionality of the application, allowing click('.some-disabled-thing') to silently do nothing seems almost certainly to be an actual logic / error condition.

@ro0gr ro0gr changed the title [breaking?] disable interacting with disabled form controls [breaking] disable interacting with disabled form controls Apr 17, 2020
@rwjblue
Copy link
Member

rwjblue commented Apr 20, 2020

@ro0gr - Would you mind rebasing (I think the conflicts are likely due to prettier changes)?

@ro0gr
Copy link
Contributor Author

ro0gr commented Apr 20, 2020

@rwjblue sure, will do, after we settle #741

@rwjblue
Copy link
Member

rwjblue commented Apr 21, 2020

OK, just rebased now that #741 and #779 have landed.

@rwjblue rwjblue merged commit a6af003 into emberjs:master Apr 21, 2020
@rwjblue rwjblue changed the title [breaking] disable interacting with disabled form controls Error when interacting with disabled form controls May 5, 2020
@amk221
Copy link
Contributor

amk221 commented Dec 21, 2020

How can we migrate our tests to support this new behaviour?

Take the following scenario:

await triggerEvent('button[disabled]', 'mouseenter');
assert.dom('.tooltip').hasText("You can't do this because reason")

This used to work, but now it throws. And, even if caught, the mouseenter event never fires.

@ijlee2
Copy link
Member

ijlee2 commented Dec 21, 2020

I replied on Discord:

I'm not sure if a mouseenter event should be fired on a disabled button. I wonder if a solution is to create a transparent element when the button is disabled and use CSS to place that element on top of the button. In test, you would mouseenter on that element instead of the button, then check what text you see in the tooltip.

Curious to learn what other approaches can be taken.

@Turbo87
Copy link
Member

Turbo87 commented Dec 21, 2020

I'm not sure if a mouseenter event should be fired on a disabled button.

if the browser fires one then we should do it here too

@amk221
Copy link
Contributor

amk221 commented Dec 22, 2020

rwjblue pointed out that even though the user can 'interact' with a disabled button, the browser won't fire the mouseenter event anyway. So I think this PR was correct - and my above code snippet is wrong - it just worked incorrectly.

@Turbo87
Copy link
Member

Turbo87 commented Dec 22, 2020

I think @rwjblue only pointed out that we should follow what the real browsers are doing, but if you're referring to the Discord conversation, then he didn't say that he knew exactly what the browsers were doing in this case. In other words: this will need to be investigated before taking any decision whether this is correct or not :)

@ro0gr
Copy link
Contributor Author

ro0gr commented Dec 22, 2020

Great finding! Ya, this seems to be a bug.

Here I've built a quick playground to found out events, which should be triggered for disabled buttons https://codepen.io/ro0gr/pen/MWjEjRE. Hope I haven't missed any events, just copy pasted all the events known to the triggerEvent.

According to the playground we do currently miss to trigger at least the following events for the disabled elements:

  • mouseenter
  • mouseleave
  • mousemove
  • mouseout
  • mouseover

Unfortunately, I'm a bit busy with some other activities right now. Will try to find some time to look at it this weekend, if someone else would not do it earlier.

@ro0gr
Copy link
Contributor Author

ro0gr commented Dec 22, 2020

oh.. seems like events mentioned above are triggered in FF only on my machine. Chrome doesn't react at all 🤔

@ro0gr
Copy link
Contributor Author

ro0gr commented Dec 22, 2020

maybe a "good" solution could be to leave the triggerEvent alone then, and let the user decide if he still wants to trigger some event over a disabled element or not. Since it can be undesirably hard to maintain a proper behavior per each browser...

@rwjblue
Copy link
Member

rwjblue commented Dec 22, 2020

I think logically being disabled means no events (e.g. the behavior added in this PR) and that matches Chrome / Edge / Safari behavior. I don't really want to have divergent by browser here in the helpers, so I think I'd prefer to take a more conservative approach (which this PR does). I do think it would be fine to add an explicit option that could be used to force an event interaction (e.g. to override this limitation), but we should have that discussion separately from this PR thread.

Overall, I think this PR did the right thing and it points out logical issues in folks' test suites (which was the point).

@rwjblue
Copy link
Member

rwjblue commented Dec 22, 2020

Oh, also, I inferred it above but I also tested this in Safari and IE11. IE11 fires events for everything regardless of disabled status, Safari fires no events on the disabled button, and FireFox only fires mousemove events (not click, dblclick, etc).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants