From 077ccac36d86f851cc80e5fe4ffc889832807162 Mon Sep 17 00:00:00 2001 From: Enrique Moreno Date: Thu, 22 May 2025 16:06:11 +0200 Subject: [PATCH 1/2] Improved test coverage --- packages/lib/jest.config.js | 5 + .../src/bulleted-list/BulletedList.test.tsx | 63 ++++- .../lib/src/data-grid/DataGrid.stories.tsx | 4 +- packages/lib/src/data-grid/DataGrid.test.tsx | 238 ++++++++++++++++-- packages/lib/src/nav-tabs/NavTabs.test.tsx | 45 +++- packages/lib/src/nav-tabs/NavTabs.tsx | 3 +- packages/lib/src/nav-tabs/Tab.tsx | 2 +- .../lib/src/status-light/StatusLight.test.tsx | 6 + .../lib/src/toggle-group/ToggleGroup.test.tsx | 83 ++++-- 9 files changed, 409 insertions(+), 40 deletions(-) diff --git a/packages/lib/jest.config.js b/packages/lib/jest.config.js index 6ba0f4d7de..9b9356956f 100644 --- a/packages/lib/jest.config.js +++ b/packages/lib/jest.config.js @@ -1,5 +1,10 @@ module.exports = { collectCoverage: true, + coveragePathIgnorePatterns: [ + "utils.ts", + "index.ts", + ".*Context\\.tsx$", // Is deprecated and will be removed in the future + ], moduleNameMapper: { "\\.(css|less|scss|sass)$": "identity-obj-proxy", "\\.(svg)$": "/test/mocks/svgMock.js", diff --git a/packages/lib/src/bulleted-list/BulletedList.test.tsx b/packages/lib/src/bulleted-list/BulletedList.test.tsx index 3ae6170d1f..1145bc207b 100644 --- a/packages/lib/src/bulleted-list/BulletedList.test.tsx +++ b/packages/lib/src/bulleted-list/BulletedList.test.tsx @@ -1,8 +1,9 @@ import { render } from "@testing-library/react"; import DxcBulletedList from "./BulletedList"; +import DxcIcon from "../icon/Icon"; describe("Bulleted list component tests", () => { - test("The component renders properly", () => { + test("The component The component renders properly", () => { const { getByText } = render( Code @@ -14,4 +15,64 @@ describe("Bulleted list component tests", () => { expect(getByText("Usage")).toBeTruthy(); expect(getByText("Specifications")).toBeTruthy(); }); + test("The component The component renders default (disc) bullets", () => { + const { container } = render( + + Item 1 + + ); + expect(container.querySelector("ul")).toBeTruthy(); + expect(container.querySelector("div")).toBeTruthy(); + }); + + test("The component renders square bullets", () => { + const { container } = render( + + Square Item + + ); + expect(container.querySelector("ul")).toBeTruthy(); + expect(container.querySelector("div")).toBeTruthy(); + expect(container.innerHTML).toContain("Square Item"); + }); + + test("The component renders circle bullets", () => { + const { container } = render( + + Circle Item + + ); + expect(container.querySelector("ul")).toBeTruthy(); + expect(container.innerHTML).toContain("Circle Item"); + }); + + test("The component renders number bullets", () => { + const { container, getByText } = render( + + Numbered Item + + ); + expect(container.querySelector("ol")).toBeTruthy(); + expect(getByText("1.")).toBeTruthy(); + }); + + test("The component renders icon bullets with icon string", () => { + const { container } = render( + + Icon Item + + ); + expect(container.querySelector("span")).toBeTruthy(); + expect(container.innerHTML).toContain("Icon Item"); + }); + + test("The component renders icon bullets with React element icon", () => { + const { container } = render( + }> + Icon React Element + + ); + expect(container.querySelector("span")).toBeTruthy(); + expect(container.innerHTML).toContain("Icon React Element"); + }); }); diff --git a/packages/lib/src/data-grid/DataGrid.stories.tsx b/packages/lib/src/data-grid/DataGrid.stories.tsx index d4e4d8708b..e78d010fc8 100644 --- a/packages/lib/src/data-grid/DataGrid.stories.tsx +++ b/packages/lib/src/data-grid/DataGrid.stories.tsx @@ -8,7 +8,7 @@ import { disabledRules } from "../../test/accessibility/rules/specific/data-grid import preview from "../../.storybook/preview"; import { userEvent, within } from "@storybook/test"; import DxcBadge from "../badge/Badge"; -import { Action } from "../table/types"; +import { ActionsCellPropsType } from "../table/types"; import { Meta, StoryObj } from "@storybook/react"; import { isKeyOfRow } from "./utils"; @@ -27,7 +27,7 @@ export default { }, } as Meta; -const actions: Action = [ +const actions: ActionsCellPropsType["actions"] = [ { title: "icon", onClick: (value?) => { diff --git a/packages/lib/src/data-grid/DataGrid.test.tsx b/packages/lib/src/data-grid/DataGrid.test.tsx index 61a0a8376f..c7ab2c67b7 100644 --- a/packages/lib/src/data-grid/DataGrid.test.tsx +++ b/packages/lib/src/data-grid/DataGrid.test.tsx @@ -1,6 +1,7 @@ -import { render } from "@testing-library/react"; +import { fireEvent, render, waitFor } from "@testing-library/react"; import DxcDataGrid from "./DataGrid"; import { GridColumn, HierarchyGridRow } from "./types"; +import { useState } from "react"; const columns: GridColumn[] = [ { @@ -13,7 +14,7 @@ const columns: GridColumn[] = [ }, { key: "complete", - label: " % Complete", + label: "% Complete", resizable: true, sortable: true, draggable: true, @@ -48,6 +49,154 @@ const expandableRows = [ }, ]; +const hierarchyRows: HierarchyGridRow[] = [ + { + name: "Root Node 1", + value: "1", + id: "a", + childRows: [ + { + name: "Child Node 1.1", + value: "1.1", + id: "aa", + childRows: [ + { + name: "Grandchild Node 1.1.1", + value: "1.1.1", + id: "aaa", + }, + { + name: "Grandchild Node 1.1.2", + value: "1.1.2", + id: "aab", + }, + ], + }, + { + name: "Child Node 1.2", + value: "1.2", + id: "ab", + }, + ], + }, + { + name: "Root Node 2", + value: "2", + id: "b", + childRows: [ + { + name: "Child Node 2.1", + value: "2.1", + id: "ba", + childRows: [ + { + name: "Grandchild Node 2.1.1", + value: "2.1.1", + id: "baa", + }, + ], + }, + { + name: "Child Node 2.2", + value: "2.2", + id: "bb", + }, + { + name: "Child Node 2.3", + value: "2.3", + id: "bc", + }, + ], + }, + { + name: "Root Node 3", + value: "3", + id: "c", + childRows: [ + { + name: "Child Node 3.1", + value: "3.1", + id: "cc", + childRows: [ + { + name: "Grandchild Node 3.1.1", + value: "3.1.1", + id: "ccc", + }, + { + name: "Grandchild Node 3.1.2", + value: "3.1.2", + id: "ccd", + }, + ], + }, + { + name: "Child Node 3.2", + value: "3.2", + id: "cd", + }, + ], + }, + { + name: "Root Node 4", + value: "4", + id: "d", + childRows: [ + { + name: "Child Node 4.1", + value: "4.1", + id: "da", + childRows: [ + { + name: "Grandchild Node 4.1.1", + value: "4.1.1", + id: "daa", + }, + ], + }, + { + name: "Child Node 4.2", + value: "4.2", + id: "dd", + }, + { + name: "Child Node 4.3", + value: "4.3", + id: "de", + }, + ], + }, + { + name: "Root Node 5", + value: "5", + id: "d", + childRows: [ + { + name: "Child Node 5.1", + value: "5.1", + id: "da", + childRows: [ + { + name: "Grandchild Node 5.1.1", + value: "5.1.1", + id: "daa", + }, + ], + }, + { + name: "Child Node 5.2", + value: "5.2", + id: "dd", + }, + { + name: "Child Node 5.3", + value: "5.3", + id: "de", + }, + ], + }, +] as HierarchyGridRow[]; + describe("Data grid component tests", () => { beforeAll(() => { (global as any).CSS = { @@ -55,24 +204,79 @@ describe("Data grid component tests", () => { }; window.HTMLElement.prototype.scrollIntoView = jest.fn; }); + test("Renders with correct content", async () => { - const { getByText, getAllByRole } = await render( - - ); + const { getByText, getAllByRole } = render(); expect(getByText("46")).toBeTruthy(); const rows = getAllByRole("row"); expect(rows.length).toBe(5); }); - // test("Content is sorted correctly", async () => { - // const { getByText, getAllByRole } = await render(); - // expect(getByText("% Complete")).toBeTruthy(); - // const headerCell = screen.getAllByRole("columnheader")[1]; - // expect(getAllByRole("gridcell")[0].textContent).toBe("1"); - // expect(headerCell.textContent).toBe(" % Complete"); - // await fireEvent.click(headerCell); - // expect(headerCell.getAttribute("aria-sort")).toBe("ascending"); - // expect(getByText("5")).toBeTruthy(); - // // await waitFor(() => expect(getAllByRole("gridcell")[0].textContent).toBe("4")); - // //waitFor(() => expect(getAllByRole("gridcell").length).toBe(8)); - // }); + + test("Renders hierarchy rows", () => { + const onSelectRows = jest.fn(); + const selectedRows = new Set(); + const { getAllByRole } = render( + + ); + const rows = getAllByRole("row"); + expect(rows.length).toBe(5); + }); + + test("Renders column headers", () => { + const { getByText } = render(); + expect(getByText("ID")).toBeTruthy(); + expect(getByText("% Complete")).toBeTruthy(); + }); + + test("Expands and collapses a row to show custom content", async () => { + const { getAllByRole, getByText, queryByText } = render( + + ); + const buttons = getAllByRole("button"); + buttons[0] && fireEvent.click(buttons[0]); + expect(getByText("Custom content 1")).toBeTruthy(); + buttons[0] && fireEvent.click(buttons[0]); + expect(queryByText("Custom content 1")).not.toBeTruthy(); + }); + + test("Sorting by column works as expected", async () => { + const { getAllByRole } = render( + + ); + const headers = getAllByRole("columnheader"); + const sortableHeader = headers[1]; + + sortableHeader && fireEvent.click(sortableHeader); + expect(sortableHeader?.getAttribute("aria-sort")).toBe("ascending"); + await waitFor(() => { + const cells = getAllByRole("gridcell"); + expect(cells[1]?.textContent).toBe("1"); + }); + sortableHeader && fireEvent.click(sortableHeader); + expect(sortableHeader?.getAttribute("aria-sort")).toBe("descending"); + await waitFor(() => { + const cells = getAllByRole("gridcell"); + expect(cells[1]?.textContent).toBe("5"); + }); + }); + + test("Expands multiple rows at once", () => { + const { getAllByRole, getByText } = render( + + ); + + const buttons = getAllByRole("button"); + buttons[0] && fireEvent.click(buttons[0]); + buttons[1] && fireEvent.click(buttons[1]); + + expect(getByText("Custom content 1")).toBeTruthy(); + expect(getByText("Custom content 2")).toBeTruthy(); + }); }); diff --git a/packages/lib/src/nav-tabs/NavTabs.test.tsx b/packages/lib/src/nav-tabs/NavTabs.test.tsx index 849c7ceea7..57a8b507d2 100644 --- a/packages/lib/src/nav-tabs/NavTabs.test.tsx +++ b/packages/lib/src/nav-tabs/NavTabs.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import DxcNavTabs from "./NavTabs"; describe("Tabs component tests", () => { @@ -69,4 +69,47 @@ describe("Tabs component tests", () => { expect(tabs[1]?.getAttribute("tabindex")).toBe("-1"); expect(tabs[2]?.getAttribute("tabindex")).toBe("3"); }); + + // test("Keyboard navigation changes focus on arrow keys", () => { + // const { getByRole, getAllByRole } = render( + // + // Tab 1 + // Tab 2 + // Tab 3 + // + // ); + + // const tablist = getByRole("tablist"); + // const tabs = getAllByRole("tab"); + + // expect(tabs[2]?.getAttribute("aria-selected")).toBe("true"); + + // fireEvent.keyDown(tablist, { key: "ArrowLeft" }); + // expect(tabs[0]?.getAttribute("tabindex")).toBe("0"); + + // fireEvent.keyDown(tablist, { key: "ArrowRight" }); + // expect(tabs[2]?.getAttribute("tabindex")).toBe("0"); + // }); + + test("Disabled tabs have aria-disabled and cannot be tab-focused", () => { + const { getAllByRole } = render( + + Disabled Tab + Active Tab + + ); + + const tabs = getAllByRole("tab"); + expect(tabs[0]?.getAttribute("aria-disabled")).toBe("true"); + expect(tabs[0]?.getAttribute("tabindex")).toBe("-1"); + }); + + test("Context passes correct iconPosition to children", () => { + const { getByText } = render( + + Tab 1 + + ); + expect(getByText("Tab 1")).toBeTruthy(); + }); }); diff --git a/packages/lib/src/nav-tabs/NavTabs.tsx b/packages/lib/src/nav-tabs/NavTabs.tsx index 880508f94e..fac2c8abc6 100644 --- a/packages/lib/src/nav-tabs/NavTabs.tsx +++ b/packages/lib/src/nav-tabs/NavTabs.tsx @@ -1,4 +1,4 @@ -import { Children, KeyboardEvent, ReactElement, useMemo, useState } from "react"; +import { Children, KeyboardEvent, MouseEvent, ReactElement, useMemo, useState } from "react"; import styled from "styled-components"; import NavTabsPropsType from "./types"; import Tab from "./Tab"; @@ -22,7 +22,6 @@ const Underline = styled.div` const DxcNavTabs = ({ iconPosition = "left", tabIndex = 0, children }: NavTabsPropsType): JSX.Element => { const [innerFocusIndex, setInnerFocusIndex] = useState(null); - const childArray = Children.toArray(children).filter( (child) => typeof child === "object" && "props" in child ) as ReactElement[]; diff --git a/packages/lib/src/nav-tabs/Tab.tsx b/packages/lib/src/nav-tabs/Tab.tsx index fda9312a8a..566f0768e0 100644 --- a/packages/lib/src/nav-tabs/Tab.tsx +++ b/packages/lib/src/nav-tabs/Tab.tsx @@ -98,7 +98,7 @@ const Tab = forwardRef( if (focusedLabel === children.toString()) { tabRef?.current?.focus(); } - }, [children, focusedLabel]); + }, [children, focusedLabel, tabRef.current]); const handleOnKeyDown = (event: KeyboardEvent) => { switch (event.key) { diff --git a/packages/lib/src/status-light/StatusLight.test.tsx b/packages/lib/src/status-light/StatusLight.test.tsx index a8b8f9d1a0..4403f4e29d 100644 --- a/packages/lib/src/status-light/StatusLight.test.tsx +++ b/packages/lib/src/status-light/StatusLight.test.tsx @@ -10,4 +10,10 @@ describe("StatusLight component tests", () => { const { getByRole } = render(); expect(getByRole("status")).toBeTruthy(); }); + + test("StatusLight dot is aria-hidden", () => { + const { container } = render(); + const dot = container.querySelector("div[aria-hidden='true']"); + expect(dot).toBeTruthy(); + }); }); diff --git a/packages/lib/src/toggle-group/ToggleGroup.test.tsx b/packages/lib/src/toggle-group/ToggleGroup.test.tsx index 1c95547373..f1f13bddd1 100644 --- a/packages/lib/src/toggle-group/ToggleGroup.test.tsx +++ b/packages/lib/src/toggle-group/ToggleGroup.test.tsx @@ -2,22 +2,15 @@ import { fireEvent, render } from "@testing-library/react"; import DxcToggleGroup from "./ToggleGroup"; const options = [ - { - value: 1, - label: "Amazon", - }, - { - value: 2, - label: "Ebay", - }, - { - value: 3, - label: "Apple", - }, - { - value: 4, - label: "Google", - }, + { value: 1, label: "Amazon" }, + { value: 2, label: "Ebay" }, + { value: 3, label: "Apple" }, + { value: 4, label: "Google" }, +]; + +const optionsWithDisabled = [ + { value: 1, label: "Amazon" }, + { value: 2, label: "Ebay", disabled: true }, ]; describe("Toggle group component tests", () => { @@ -93,4 +86,62 @@ describe("Toggle group component tests", () => { const toggleGroup = getByRole("toolbar"); expect(toggleGroup.getAttribute("aria-orientation")).toBe("vertical"); }); + test("Keyboard 'Enter' triggers onChange", () => { + const onChange = jest.fn(); + const { getByText } = render(); + const option = getByText("Amazon"); + option.focus(); + fireEvent.keyDown(option, { key: "Enter" }); + expect(onChange).toHaveBeenCalledTimes(1); + }); + test("Keyboard 'Space' triggers onChange", () => { + const onChange = jest.fn(); + const { getByText } = render(); + const option = getByText("Amazon"); + option.focus(); + fireEvent.keyDown(option, { key: " " }); + expect(onChange).toHaveBeenCalledTimes(1); + }); + test("Clicking a disabled button does not call onChange", () => { + const onChange = jest.fn(); + const { getByText } = render(); + const disabledOption = getByText("Ebay"); + fireEvent.click(disabledOption); + expect(onChange).not.toHaveBeenCalled(); + }); + test("Button only renders icon if label is missing", () => { + const iconOnlyOption = [{ value: 1, icon: Icon, title: "Icon only" }]; + const { queryByText } = render(); + expect(queryByText("Icon")).toBeTruthy(); + expect(queryByText("Icon only")).toBeFalsy(); + }); + test("Disabled buttons have tabIndex -1", () => { + const { getAllByRole } = render(); + const buttons = getAllByRole("button"); + expect(buttons[0]?.getAttribute("tabindex")).toBe("0"); + expect(buttons[1]?.getAttribute("tabindex")).toBe("-1"); + }); + test("Removes selected value when multiple is true and value is controlled", () => { + const handleChange = jest.fn(); + const options = [ + { value: 1, label: "Amazon" }, + { value: 2, label: "Ebay" }, + ]; + const { getByRole } = render(); + + fireEvent.click(getByRole("button", { name: "Ebay" })); + expect(handleChange).toHaveBeenCalledWith([1]); + }); + + test("Adds value when multiple is true and value is controlled", () => { + const handleChange = jest.fn(); + const options = [ + { value: 1, label: "Amazon" }, + { value: 2, label: "Ebay" }, + ]; + const { getByRole } = render(); + + fireEvent.click(getByRole("button", { name: "Ebay" })); + expect(handleChange).toHaveBeenCalledWith([1, 2]); + }); }); From 8c1e38f505d66d9ed268fc7bde32817963340db2 Mon Sep 17 00:00:00 2001 From: Enrique Moreno Date: Mon, 26 May 2025 16:43:23 +0200 Subject: [PATCH 2/2] Added fixes from review --- .../src/bulleted-list/BulletedList.test.tsx | 35 ++++++------------- packages/lib/src/data-grid/DataGrid.test.tsx | 1 - packages/lib/src/nav-tabs/NavTabs.test.tsx | 2 +- packages/lib/src/nav-tabs/NavTabs.tsx | 2 +- packages/lib/src/nav-tabs/Tab.tsx | 2 +- .../lib/src/toggle-group/ToggleGroup.test.tsx | 12 +++++-- 6 files changed, 22 insertions(+), 32 deletions(-) diff --git a/packages/lib/src/bulleted-list/BulletedList.test.tsx b/packages/lib/src/bulleted-list/BulletedList.test.tsx index 1145bc207b..0ac2c5fe7b 100644 --- a/packages/lib/src/bulleted-list/BulletedList.test.tsx +++ b/packages/lib/src/bulleted-list/BulletedList.test.tsx @@ -3,7 +3,7 @@ import DxcBulletedList from "./BulletedList"; import DxcIcon from "../icon/Icon"; describe("Bulleted list component tests", () => { - test("The component The component renders properly", () => { + test("The component renders properly", () => { const { getByText } = render( Code @@ -15,7 +15,7 @@ describe("Bulleted list component tests", () => { expect(getByText("Usage")).toBeTruthy(); expect(getByText("Specifications")).toBeTruthy(); }); - test("The component The component renders default (disc) bullets", () => { + test("The component renders default (disc) bullets", () => { const { container } = render( Item 1 @@ -25,27 +25,6 @@ describe("Bulleted list component tests", () => { expect(container.querySelector("div")).toBeTruthy(); }); - test("The component renders square bullets", () => { - const { container } = render( - - Square Item - - ); - expect(container.querySelector("ul")).toBeTruthy(); - expect(container.querySelector("div")).toBeTruthy(); - expect(container.innerHTML).toContain("Square Item"); - }); - - test("The component renders circle bullets", () => { - const { container } = render( - - Circle Item - - ); - expect(container.querySelector("ul")).toBeTruthy(); - expect(container.innerHTML).toContain("Circle Item"); - }); - test("The component renders number bullets", () => { const { container, getByText } = render( @@ -67,12 +46,18 @@ describe("Bulleted list component tests", () => { }); test("The component renders icon bullets with React element icon", () => { + const icon = ( + + + + + ); const { container } = render( - }> + Icon React Element ); - expect(container.querySelector("span")).toBeTruthy(); + expect(container.querySelector("svg")).toBeTruthy(); expect(container.innerHTML).toContain("Icon React Element"); }); }); diff --git a/packages/lib/src/data-grid/DataGrid.test.tsx b/packages/lib/src/data-grid/DataGrid.test.tsx index c7ab2c67b7..337a79916a 100644 --- a/packages/lib/src/data-grid/DataGrid.test.tsx +++ b/packages/lib/src/data-grid/DataGrid.test.tsx @@ -1,7 +1,6 @@ import { fireEvent, render, waitFor } from "@testing-library/react"; import DxcDataGrid from "./DataGrid"; import { GridColumn, HierarchyGridRow } from "./types"; -import { useState } from "react"; const columns: GridColumn[] = [ { diff --git a/packages/lib/src/nav-tabs/NavTabs.test.tsx b/packages/lib/src/nav-tabs/NavTabs.test.tsx index 57a8b507d2..a18a9868cc 100644 --- a/packages/lib/src/nav-tabs/NavTabs.test.tsx +++ b/packages/lib/src/nav-tabs/NavTabs.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render } from "@testing-library/react"; +import { render } from "@testing-library/react"; import DxcNavTabs from "./NavTabs"; describe("Tabs component tests", () => { diff --git a/packages/lib/src/nav-tabs/NavTabs.tsx b/packages/lib/src/nav-tabs/NavTabs.tsx index fac2c8abc6..923890b92f 100644 --- a/packages/lib/src/nav-tabs/NavTabs.tsx +++ b/packages/lib/src/nav-tabs/NavTabs.tsx @@ -1,4 +1,4 @@ -import { Children, KeyboardEvent, MouseEvent, ReactElement, useMemo, useState } from "react"; +import { Children, KeyboardEvent, ReactElement, useMemo, useState } from "react"; import styled from "styled-components"; import NavTabsPropsType from "./types"; import Tab from "./Tab"; diff --git a/packages/lib/src/nav-tabs/Tab.tsx b/packages/lib/src/nav-tabs/Tab.tsx index 566f0768e0..fda9312a8a 100644 --- a/packages/lib/src/nav-tabs/Tab.tsx +++ b/packages/lib/src/nav-tabs/Tab.tsx @@ -98,7 +98,7 @@ const Tab = forwardRef( if (focusedLabel === children.toString()) { tabRef?.current?.focus(); } - }, [children, focusedLabel, tabRef.current]); + }, [children, focusedLabel]); const handleOnKeyDown = (event: KeyboardEvent) => { switch (event.key) { diff --git a/packages/lib/src/toggle-group/ToggleGroup.test.tsx b/packages/lib/src/toggle-group/ToggleGroup.test.tsx index f1f13bddd1..941434b249 100644 --- a/packages/lib/src/toggle-group/ToggleGroup.test.tsx +++ b/packages/lib/src/toggle-group/ToggleGroup.test.tsx @@ -110,9 +110,15 @@ describe("Toggle group component tests", () => { expect(onChange).not.toHaveBeenCalled(); }); test("Button only renders icon if label is missing", () => { - const iconOnlyOption = [{ value: 1, icon: Icon, title: "Icon only" }]; - const { queryByText } = render(); - expect(queryByText("Icon")).toBeTruthy(); + const icon = ( + + + + + ); + const iconOnlyOption = [{ value: 1, icon: icon, title: "Icon only" }]; + const { container, queryByText } = render(); + expect(container.querySelector("svg")).toBeTruthy(); expect(queryByText("Icon only")).toBeFalsy(); }); test("Disabled buttons have tabIndex -1", () => {