From df282f95c3ba6df7dece1a770e6b4e69a3bc4274 Mon Sep 17 00:00:00 2001 From: Ben Monro Date: Fri, 3 Jan 2020 08:11:22 -0800 Subject: [PATCH] fix: account for sorting bug in node 10 (#205) * fix: account for sorting bug in node 10 * just one sort regardless of node version --- __tests__/react/tab.js | 27 ++++++++++++++++++++++++++- src/index.js | 25 ++++++++++++++----------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/__tests__/react/tab.js b/__tests__/react/tab.js index 55b3e5e8..fcd25279 100644 --- a/__tests__/react/tab.js +++ b/__tests__/react/tab.js @@ -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( <>
@@ -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 => ( + + ))} + + ); + + expect.assertions(26); + + letters.split("").forEach(letter => { + userEvent.tab(); + expect(getByTestId(letter)).toHaveFocus(); + }); + }); }); diff --git a/src/index.js b/src/index.js index 043ef857..ba53d28c 100644 --- a/src/index.js +++ b/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); }); } @@ -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)