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

fix: account for sorting bug in node 10 #205

Merged
merged 2 commits into from Jan 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 26 additions & 1 deletion __tests__/react/tab.js
Expand Up @@ -157,7 +157,7 @@ describe("userEvent.tab", () => {
expect(link).toHaveFocus();
});

it("should stay within a focus trab", () => {
it("should stay within a focus trap", () => {
const { getAllByTestId, getByTestId } = render(
<>
<div data-testid="div1">
Expand Down Expand Up @@ -219,4 +219,29 @@ describe("userEvent.tab", () => {
// cycle goes back to first element
expect(checkbox2).toHaveFocus();
});

// prior to node 11, Array.sort was unstable for arrays w/ length > 10.
// https://twitter.com/mathias/status/1036626116654637057
// for this reason, the tab() function needs to account for this in it's sorting.
// for example under node 10 in this test:
// > 'abcdefghijklmnopqrstuvwxyz'.split('').sort(() => 0).join('')
// will give you 'nacdefghijklmbopqrstuvwxyz'
it("should support unstable sorting environments like node 10", () => {
const letters = "abcdefghijklmnopqrstuvwxyz";

const { getByTestId } = render(
<>
{letters.split("").map(letter => (
<input key={letter} type="text" data-testid={letter} />
))}
</>
);

expect.assertions(26);

letters.split("").forEach(letter => {
userEvent.tab();
expect(getByTestId(letter)).toHaveFocus();
});
});
});
25 changes: 14 additions & 11 deletions src/index.js
@@ -1,7 +1,7 @@
import { fireEvent } from "@testing-library/dom";

function wait(time) {
return new Promise(function(resolve) {
return new Promise(function (resolve) {
setTimeout(() => resolve(), time);
});
}
Expand Down Expand Up @@ -232,21 +232,24 @@ const userEvent = {
const focusableElements = focusTrap.querySelectorAll(
"input, button, select, textarea, a[href], [tabindex]"
);
const list = Array.prototype.filter
.call(focusableElements, function(item) {
return item.getAttribute("tabindex") !== "-1";
})
let list = Array.prototype.filter.call(focusableElements, function (item) {
return item.getAttribute("tabindex") !== "-1";
}).map((el, idx) => ({ el, idx }))
.sort((a, b) => {
const tabIndexA = a.getAttribute("tabindex");
const tabIndexB = b.getAttribute("tabindex");
return tabIndexA < tabIndexB ? -1 : tabIndexA > tabIndexB ? 1 : 0;
});
const index = list.indexOf(document.activeElement);
const tabIndexA = a.el.getAttribute("tabindex");
const tabIndexB = b.el.getAttribute("tabindex");

const diff = tabIndexA - tabIndexB;

return diff !== 0 ? diff : a.idx - b.idx;
})

const index = list.findIndex(({ el }) => el === document.activeElement);

let nextIndex = shift ? index - 1 : index + 1;
let defaultIndex = shift ? list.length - 1 : 0;

const next = list[nextIndex] || list[defaultIndex];
const { el: next } = (list[nextIndex] || list[defaultIndex]);

if (next.getAttribute("tabindex") === null) {
next.setAttribute("tabindex", "0"); // jsdom requires tabIndex=0 for an item to become 'document.activeElement' (the browser does not)
Expand Down