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

.focus() does not fire focus events if window is not focussed #553

Open
FritsvanCampen opened this issue Feb 1, 2021 · 2 comments
Open
Labels
needs specification The desired behavior is not defined yet

Comments

@FritsvanCampen
Copy link

FritsvanCampen commented Feb 1, 2021

Browsers (confirmed in Chrome/Firefox) will not dispatch focus events when the window is not focused when calling .focus().
Here is a codepen that demonstrates this behavior: https://codepen.io/fritsvancampen/pen/OJbPaLX

This becomes a problem if you're using a test-runner that uses a real browser to test (such as Karma) and you're running tests locally, or testing with more than one browser at once (ie Chrome and Firefox). Only one browser window can have focus at a time so your code under test is going to behave differently. You can imagine the kind of debug-hell you end up in, it's an actual Heisenbug!

I understand that if you're using a headless browser or a mocked browser this is not an issue.

Is this a use-case that this library wants to support?


I have been tinkering with a workaround but I haven't found a perfect solution but perhaps we don't need a perfect solution.

Instead of calling .focus() you can fire the focus events manually (with dispatchEvent/fireEvent), and set the activeElement. However the latter doesn't get picked up by CSS which might be a problem because of :focus rules.

Another possibility is to fire the focus events and call .focus() (to move the activeElement). This has the same problem: it also doesn't trigger :focus CSS-rules. This also generates duplicate events, which is acceptable for my use case.

Trying to retro-actively apply the workaround to this library is a bit of a headache so an integrated solution would be nice to have. I would find it acceptable to activate this workaround behavior through some kind of config.

Here is a codepen with the workaround: https://codepen.io/fritsvancampen/pen/VwmYVQe


I would offer a PR but I can't seem to get the test-suite running (detects 0 tests).

@ph-fritsche
Copy link
Member

I don't think this is entirely "fixable" as it is immanent to how the real browser applies the concept of focus:

  • document.activeElement refers to the active element inside that document.
  • If the user switches out of the context of a document, the activeElement does not change.
  • :focus and the blur/focus events refer to the element a user interaction e.g. per keyboard would act upon.

If you programmatically focus an element, the browser does change the activeElement no matter if the document is the context the user is in. However if there is a BlurEvent and/or FocusEvent depends on an element actually losing/gaining the focus of user interaction.

  1. start with an element A that is both document.activeElement and :focus.
  2. switch out of the context of the document - e.g. focus the address bar or tab to another window
  3. a BlurEvent is dispatched on element A - element A loses :focus, but is still activeElement
  4. call focus() on element B
  5. element B is activeElement but does not become :focus - no FocusEvent is dispatched
  6. switch back to the context of the document
  7. a FocusEvent is dispatched on element B - element B becomes :focus

What we could do:

  • Check if the activeElement is :focus. If it is not, dispatch a blur respectively focus event.

What we can not do:

  • Control :focus
  • Bar the browser from firing additional blur/focus events when the active window changes.

https://codesandbox.io/s/gifted-gauss-49ecm?file=/src/index.js

@ph-fritsche ph-fritsche added the needs specification The desired behavior is not defined yet label Mar 16, 2021
@rothsandro
Copy link

We have an issue that is related to this one. We use Angular + Karma and some of our inputs use the blur event (either directly or via third-party directive). In our tests we use userEvent.type() to enter value. Because of the problem in this issue the blur event is not fired which breaks everything.

I was able to reproduce the problem in a new, simple Angular app with an input and a blur event listener that just does value.toUpperCase(). When running the tests for the first time it always works, the blur event is fired. When rerunning the tests (adding a new line and saving the file) the window is not focused anymore and the tests fails because of the missing blur event. When I add some delay in the test and use that delay to focus the Chrome window again it works, the test is passing.

The exact same test in React + Jest works fine and I guess it would also work with Angular + Jest (but switching to Jest is not an option for us).

Our current workaround is to use fireEvent.focus() and fireEvent.blur() to manually trigger these events. It's an easy fix but I don't like it because I think these events are implementation details that tests should not care about.


Related (for Firefox): karma-runner/karma-firefox-launcher#56

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs specification The desired behavior is not defined yet
Projects
None yet
Development

No branches or pull requests

3 participants