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

Keyboard arrows navigation not causing input[type=radio] to change focus #843

Closed
mwojslaw opened this issue Jan 26, 2022 · 13 comments · Fixed by #995
Closed

Keyboard arrows navigation not causing input[type=radio] to change focus #843

mwojslaw opened this issue Jan 26, 2022 · 13 comments · Fixed by #995
Labels
accuracy Improves the accuracy of how behavior is simulated enhancement New feature or request released

Comments

@mwojslaw
Copy link

  • @testing-library/user-event version: 13.5.0
  • Testing Framework and version: jest - v27.4.7, @testing-library/jest-dom - v5.16.1,
  • DOM Environment: jsdom - v19.0.0

Relevant code or config

it('radio and keyboard navigation', async () => {
  render(
    <>
      <input name="test" aria-label="1" type="radio" />
      <input name="test" aria-label="2" type="radio" />
    </>
  );
  userEvent.tab();
  
  // Here we're fine
  expect(screen.getByLabelText('1')).toHaveFocus();
  
  userEvent.keyboard('{arrowdown}');
  
  // Here we're not
  expect(screen.getByLabelText('2')).toHaveFocus();
});

What you did:

Trigger keyboard navigation on input type radio.

What happened:

  • Focus is not changing accordingly.
  • onchange event is not triggered as well.
@ph-fritsche ph-fritsche added accuracy Improves the accuracy of how behavior is simulated enhancement New feature or request labels Jan 26, 2022
@tol-is
Copy link

tol-is commented Feb 17, 2022

Hey @mwojslaw, I'm having the exact same issue and ran out of threads to follow. Did you manage to work around it somehow?

@mwojslaw
Copy link
Author

Not really, I skipped the test for now

@tol-is
Copy link

tol-is commented Feb 18, 2022

Yeah, same here, I assume it's jsdom that doesn't implement this behavior.

Thanks!

@kboedges
Copy link

kboedges commented Feb 25, 2022

Having this issue as well, userEvent.tab() gives focus to elements just as expected, but anything concerning userEvent.keyboard(...) isn't functioning for me.

@Crustan
Copy link

Crustan commented Feb 28, 2022

I have the same issue but with userEvent.keyboard('[Space]'). I have no problem with [Enter].

I have tried:

  • userEvent.keyboard('[Space]')
  • userEvent.keyboard('{Space}')
  • userEvent.keyboard(' ')

My test looks like this, and it should toggle on Space or Enter when focused:

it('should toggle expandable section on focus + space or enter keypress', async () => {
    render(<Component isExpandable items={[item]} title='Expandable' />);
    expect(screen.queryByText(item.name)).not.toBeInTheDocument();

    const heading = screen.getByRole('button', { name: /expandable/i, expanded: false });
    userEvent.tab();
    expect(heading).toHaveFocus();
    userEvent.keyboard('{space}');  // This doesn't works
    // userEvent.type(heading, '{space}'); // This works
    // userEvent.keyboard('[Enter]'); // This also works

    const itemName = await screen.findByText(item.name);
    expect(itemName).toBeInTheDocument();
  });

@ph-fritsche
Copy link
Member

Please keep separate issues separate.
Default behavior for arrow keys on radio buttons is not implemented yet.
PR would be welcome.

@kboedges
Copy link

What do you mean by default behavior?

@ph-fritsche
Copy link
Member

@kboedges
When a trusted event is dispatched as result of a user interaction and no event handler prevents it, the browser implements default behavior for some events, e.g. inserting a character into the DOM at the cursor/selection on a keypress.
These events can not be dispatched programmatically, so this library dispatches look-alike events and implements the default behavior a browser usually implements on the trusted equivalent.
For keydown events for arrow keys the default behavior on radio buttons - changing focus in the radio group - is not yet implemented.

@JRJurman
Copy link

JRJurman commented May 8, 2022

@ph-fritsche can you clarify - is this a change that is required in user-event and not jsdom? Can you point to examples in this repository where additional behaviors (such as changing focus) are implemented for a specific action / component?

@ph-fritsche
Copy link
Member

@JRJurman Default behavior on events is reimplemented in https://github.com/testing-library/user-event/tree/main/src/event/behavior
Focus can also be changed per pointer not being the result of an event - see

function mousedownDefaultBehavior({
position,
target,
targetIsDisabled,
clickCount,
node,
offset,
}: {
position: NonNullable<pointerState['position']['string']>
target: Element
targetIsDisabled: boolean
clickCount: number
node?: Node
offset?: number
}) {
// An unprevented mousedown moves the cursor to the closest character.
// We try to approximate the behavior for a no-layout environment.
if (!targetIsDisabled) {
const hasValue = isElementType(target, ['input', 'textarea'])
// On non-input elements the text selection per multiple click
// can extend beyond the target boundaries.
// The exact mechanism what is considered in the same line is unclear.
// Looks it might be every inline element.
// TODO: Check what might be considered part of the same line of text.
const text = String(hasValue ? getUIValue(target) : target.textContent)
const [start, end] = node
? // As offset is describing a DOMOffset it is non-trivial to determine
// which elements might be considered in the same line of text.
// TODO: support expanding initial range on multiple clicks if node is given
[offset, offset]
: getTextRange(text, offset, clickCount)
// TODO: implement modifying selection per shift/ctrl+mouse
if (hasValue) {
setUISelection(target, {
anchorOffset: start ?? text.length,
focusOffset: end ?? text.length,
})
position.selectionRange = {
node: target,
start: start ?? 0,
end: end ?? text.length,
}
} else {
const {node: startNode, offset: startOffset} = resolveSelectionTarget({
target,
node,
offset: start,
})
const {node: endNode, offset: endOffset} = resolveSelectionTarget({
target,
node,
offset: end,
})
const range = target.ownerDocument.createRange()
range.setStart(startNode, startOffset)
range.setEnd(endNode, endOffset)
position.selectionRange = range
const selection = target.ownerDocument.getSelection() as Selection
selection.removeAllRanges()
selection.addRange(range.cloneRange())
}
}
// The closest focusable element is focused when a `mousedown` would have been fired.
// Even if there was no `mousedown` because the element was disabled.
// A `mousedown` that preventsDefault cancels this though.
focus(target)
}

@github-actions
Copy link

🎉 This issue has been resolved in version 14.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@ph-fritsche
Copy link
Member

@all-contributors add @mwojslaw ideas

@allcontributors
Copy link
Contributor

@ph-fritsche

I've put up a pull request to add @mwojslaw! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accuracy Improves the accuracy of how behavior is simulated enhancement New feature or request released
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants