Skip to content

Commit

Permalink
fix: account for sorting bug in node 10 (#205)
Browse files Browse the repository at this point in the history
* fix: account for sorting bug in node 10

* just one sort regardless of node version
  • Loading branch information
benmonro authored and Kent C. Dodds committed Jan 3, 2020
1 parent b4b5318 commit df282f9
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
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

0 comments on commit df282f9

Please sign in to comment.