Skip to content

Commit df282f9

Browse files
benmonroKent C. Dodds
authored and
Kent C. Dodds
committed
fix: account for sorting bug in node 10 (testing-library#205)
* fix: account for sorting bug in node 10 * just one sort regardless of node version
1 parent b4b5318 commit df282f9

File tree

2 files changed

+40
-12
lines changed

2 files changed

+40
-12
lines changed

__tests__/react/tab.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ describe("userEvent.tab", () => {
157157
expect(link).toHaveFocus();
158158
});
159159

160-
it("should stay within a focus trab", () => {
160+
it("should stay within a focus trap", () => {
161161
const { getAllByTestId, getByTestId } = render(
162162
<>
163163
<div data-testid="div1">
@@ -219,4 +219,29 @@ describe("userEvent.tab", () => {
219219
// cycle goes back to first element
220220
expect(checkbox2).toHaveFocus();
221221
});
222+
223+
// prior to node 11, Array.sort was unstable for arrays w/ length > 10.
224+
// https://twitter.com/mathias/status/1036626116654637057
225+
// for this reason, the tab() function needs to account for this in it's sorting.
226+
// for example under node 10 in this test:
227+
// > 'abcdefghijklmnopqrstuvwxyz'.split('').sort(() => 0).join('')
228+
// will give you 'nacdefghijklmbopqrstuvwxyz'
229+
it("should support unstable sorting environments like node 10", () => {
230+
const letters = "abcdefghijklmnopqrstuvwxyz";
231+
232+
const { getByTestId } = render(
233+
<>
234+
{letters.split("").map(letter => (
235+
<input key={letter} type="text" data-testid={letter} />
236+
))}
237+
</>
238+
);
239+
240+
expect.assertions(26);
241+
242+
letters.split("").forEach(letter => {
243+
userEvent.tab();
244+
expect(getByTestId(letter)).toHaveFocus();
245+
});
246+
});
222247
});

src/index.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { fireEvent } from "@testing-library/dom";
22

33
function wait(time) {
4-
return new Promise(function(resolve) {
4+
return new Promise(function (resolve) {
55
setTimeout(() => resolve(), time);
66
});
77
}
@@ -232,21 +232,24 @@ const userEvent = {
232232
const focusableElements = focusTrap.querySelectorAll(
233233
"input, button, select, textarea, a[href], [tabindex]"
234234
);
235-
const list = Array.prototype.filter
236-
.call(focusableElements, function(item) {
237-
return item.getAttribute("tabindex") !== "-1";
238-
})
235+
let list = Array.prototype.filter.call(focusableElements, function (item) {
236+
return item.getAttribute("tabindex") !== "-1";
237+
}).map((el, idx) => ({ el, idx }))
239238
.sort((a, b) => {
240-
const tabIndexA = a.getAttribute("tabindex");
241-
const tabIndexB = b.getAttribute("tabindex");
242-
return tabIndexA < tabIndexB ? -1 : tabIndexA > tabIndexB ? 1 : 0;
243-
});
244-
const index = list.indexOf(document.activeElement);
239+
const tabIndexA = a.el.getAttribute("tabindex");
240+
const tabIndexB = b.el.getAttribute("tabindex");
241+
242+
const diff = tabIndexA - tabIndexB;
243+
244+
return diff !== 0 ? diff : a.idx - b.idx;
245+
})
246+
247+
const index = list.findIndex(({ el }) => el === document.activeElement);
245248

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

249-
const next = list[nextIndex] || list[defaultIndex];
252+
const { el: next } = (list[nextIndex] || list[defaultIndex]);
250253

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

0 commit comments

Comments
 (0)