From c9d045c1e80caf001cfc1e53d14652dba6a8c306 Mon Sep 17 00:00:00 2001 From: "tom.davies" Date: Thu, 26 Sep 2024 15:40:34 +0100 Subject: [PATCH 1/2] refactor(useInputBehaviour): refactor enzyme tests to RTL --- .../useInputBehaviour.spec.tsx | 122 ------------- .../useInputBehaviour.test.tsx | 172 ++++++++++++++++++ 2 files changed, 172 insertions(+), 122 deletions(-) delete mode 100644 src/__internal__/input-behaviour/useInputBehaviour.spec.tsx create mode 100644 src/__internal__/input-behaviour/useInputBehaviour.test.tsx diff --git a/src/__internal__/input-behaviour/useInputBehaviour.spec.tsx b/src/__internal__/input-behaviour/useInputBehaviour.spec.tsx deleted file mode 100644 index ae457ac6ba..0000000000 --- a/src/__internal__/input-behaviour/useInputBehaviour.spec.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import { act } from "react-dom/test-utils"; -import { mount, ReactWrapper } from "enzyme"; - -import useInputBehaviour from "./useInputBehaviour"; - -const Input = styled.input``; - -const HookTestComponent = ({ - blockGroupBehaviour, -}: { - blockGroupBehaviour?: boolean; -}) => { - const { inputRef, ...hookValues } = useInputBehaviour(blockGroupBehaviour); - return ( - { - if (inputRef) inputRef({ current: input }); - }} - /> - ); -}; - -const render = (blockGroupBehaviour?: boolean) => - mount(, { - attachTo: document.getElementById("enzymeContainer"), - }); - -describe("useInputBehaviour", () => { - let wrapper: ReactWrapper; - let container: HTMLDivElement | null; - - describe("group behaviour enabled", () => { - beforeEach(() => { - container = document.createElement("div"); - container.id = "enzymeContainer"; - document.body.appendChild(container); - wrapper = render(); - }); - - afterEach(() => { - if (container && container.parentNode) { - container.parentNode.removeChild(container); - } - - container = null; - }); - - it("toggles hasFocus when element is focused and blurred", () => { - act(() => { - wrapper.find(Input).props().onFocus(); - }); - wrapper.update(); - expect(wrapper.find(Input).props().hasFocus).toBe(true); - - act(() => { - wrapper.find(Input).props().onBlur(); - }); - wrapper.update(); - expect(wrapper.find(Input).props().hasFocus).toBe(false); - }); - - it("toggles hasMouseOver when mouse enters and leaves the element", () => { - act(() => { - wrapper.find(Input).props().onMouseEnter(); - }); - wrapper.update(); - expect(wrapper.find(Input).props().hasMouseOver).toBe(true); - - act(() => { - wrapper.find(Input).props().onMouseLeave(); - }); - wrapper.update(); - expect(wrapper.find(Input).props().hasMouseOver).toBe(false); - }); - - it("focuses the element on mousedown event", () => { - const rafSpy = jest - .spyOn(window, "requestAnimationFrame") - .mockImplementation((callback: FrameRequestCallback): number => { - callback(0); - return 0; - }); - - act(() => { - wrapper.find(Input).props().onMouseDown(); - }); - wrapper.update(); - expect( - document.activeElement === wrapper.find(Input).getDOMNode() - ).toEqual(true); - rafSpy.mockRestore(); - }); - }); - - describe("group behaviour disabled", () => { - beforeEach(() => { - container = document.createElement("div"); - container.id = "enzymeContainer"; - document.body.appendChild(container); - wrapper = render(true); - }); - - afterEach(() => { - if (container && container.parentNode) { - container.parentNode.removeChild(container); - } - - container = null; - }); - - it("blocks the group behaviour callbacks when prop is true", () => { - expect(wrapper.find(Input).props().onMouseEnter).toEqual(undefined); - expect(wrapper.find(Input).props().onMouseLeave).toEqual(undefined); - expect(wrapper.find(Input).props().onBlur).toEqual(undefined); - expect(wrapper.find(Input).props().onFocus).toEqual(undefined); - }); - }); -}); diff --git a/src/__internal__/input-behaviour/useInputBehaviour.test.tsx b/src/__internal__/input-behaviour/useInputBehaviour.test.tsx new file mode 100644 index 0000000000..536b77dafb --- /dev/null +++ b/src/__internal__/input-behaviour/useInputBehaviour.test.tsx @@ -0,0 +1,172 @@ +import React from "react"; +import { act, renderHook } from "@testing-library/react-hooks"; +import { render, screen } from "@testing-library/react"; +import { Input } from "../input"; + +import useInputBehaviour from "./useInputBehaviour"; + +test("when `inputRef` is passed to the input, it assigns the input element reference correctly", () => { + const { result } = renderHook(() => useInputBehaviour()); + const { inputRef } = result.current; + + let capturedInput: HTMLInputElement | null = null; + + const inputRefCallback = (input: HTMLInputElement | null) => { + if (inputRef && input) { + inputRef({ current: input }); + capturedInput = input; + } + }; + + render(); + + const input = screen.getByRole("textbox") as HTMLInputElement; + + expect(capturedInput).toBe(input); +}); + +test("when group behaviour is enabled and the `onFocus` function is called, the `hasFocus` hook returns true", () => { + const { result } = renderHook(() => useInputBehaviour()); + const { onFocus } = result.current; + + act(() => { + onFocus?.(); + }); + + expect(result.current.hasFocus).toBe(true); +}); + +test("when group behaviour is enabled and the `onBlur` function is called, the `hasFocus` hook returns false", () => { + const { result } = renderHook(() => useInputBehaviour()); + const { onFocus, onBlur } = result.current; + + act(() => { + onFocus?.(); + }); + + expect(result.current.hasFocus).toBe(true); + + act(() => { + onBlur?.(); + }); + + expect(result.current.hasFocus).toBe(false); +}); + +test("when group behaviour is enabled and the `onMouseDown` function is called, the input is focused and `preventScroll` within the focus method is set to true", () => { + const rafSpy = jest + .spyOn(window, "requestAnimationFrame") + .mockImplementation((callback: FrameRequestCallback): number => { + callback(0); + return 0; + }); + + const { result } = renderHook(() => useInputBehaviour()); + const { inputRef, onMouseDown } = result.current; + + const inputRefCallback = (input: HTMLInputElement | null) => { + if (inputRef && input) { + inputRef({ current: input }); + } + }; + + render(); + + const input = screen.getByRole("textbox") as HTMLInputElement; + const focusSpy = jest.spyOn(input, "focus"); + + act(() => { + onMouseDown?.(); + }); + + expect(input).toHaveFocus(); + expect(focusSpy).toHaveBeenCalledWith({ preventScroll: true }); + + rafSpy.mockRestore(); + focusSpy.mockRestore(); +}); + +test("when group behaviour is enabled and the `onMouseEnter` function is called the `hasMouseOver` hook returns true", () => { + const { result } = renderHook(() => useInputBehaviour()); + const { onMouseEnter } = result.current; + + act(() => { + onMouseEnter?.(); + }); + + expect(result.current.hasMouseOver).toBe(true); +}); + +test("when group behaviour is enabled and the `onMouseLeave` function is called the `hasMouseOver` hook returns false", async () => { + const { result } = renderHook(() => useInputBehaviour()); + const { onMouseEnter, onMouseLeave } = result.current; + + act(() => { + onMouseEnter?.(); + }); + + expect(result.current.hasMouseOver).toBe(true); + + act(() => { + onMouseLeave?.(); + }); + + expect(result.current.hasMouseOver).toBe(false); +}); + +/* Passing true as a param of `useInputBehaviour` sets the `blockInputBehaviour` prop in `useInputBehaviour` to be true` */ +test("when group behaviour is disabled and the `onFocus` function is called, the `hasFocus` hook returns false", () => { + const { result } = renderHook(() => useInputBehaviour(true)); + const { onFocus } = result.current; + + act(() => { + onFocus?.(); + }); + + expect(result.current.hasFocus).toBe(false); +}); + +test("when group behaviour is disabled and the `onBlur` function is called, the `hasFocus` hook returns false", () => { + const { result } = renderHook(() => useInputBehaviour(true)); + const { onFocus, onBlur } = result.current; + + act(() => { + onFocus?.(); + }); + + expect(result.current.hasFocus).toBe(false); + + act(() => { + onBlur?.(); + }); + + expect(result.current.hasFocus).toBe(false); +}); + +test("when group behaviour is disabled and the `onMouseEnter` function is called the `hasMouseOver` hook returns false", () => { + const { result } = renderHook(() => useInputBehaviour(true)); + const { onMouseEnter } = result.current; + + act(() => { + onMouseEnter?.(); + }); + + expect(result.current.hasMouseOver).toBe(false); +}); + +test("when group behaviour is disabled and the `onMouseLeave` function is called the `hasMouseOver` hook returns false", async () => { + const { result } = renderHook(() => useInputBehaviour(true)); + const { onMouseEnter, onMouseLeave } = result.current; + + act(() => { + onMouseEnter?.(); + }); + + expect(result.current.hasMouseOver).toBe(false); + + act(() => { + onMouseLeave?.(); + }); + + expect(result.current.hasMouseOver).toBe(false); +}); From d47cf83f3d5c0cc01a4dd53a594865a21987d3a4 Mon Sep 17 00:00:00 2001 From: "tom.davies" Date: Fri, 27 Sep 2024 15:33:51 +0100 Subject: [PATCH 2/2] refactor(input-icon-toggle): refactor enzyme tests to RTL --- .../input-icon-toggle.spec.tsx | 300 --------------- .../input-icon-toggle.test.tsx | 358 ++++++++++++++++++ 2 files changed, 358 insertions(+), 300 deletions(-) delete mode 100644 src/__internal__/input-icon-toggle/input-icon-toggle.spec.tsx create mode 100644 src/__internal__/input-icon-toggle/input-icon-toggle.test.tsx diff --git a/src/__internal__/input-icon-toggle/input-icon-toggle.spec.tsx b/src/__internal__/input-icon-toggle/input-icon-toggle.spec.tsx deleted file mode 100644 index 544c25d765..0000000000 --- a/src/__internal__/input-icon-toggle/input-icon-toggle.spec.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import React from "react"; -import { shallow, mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; - -import { assertStyleMatch } from "../../__spec_helper__/__internal__/test-utils"; -import ValidationIconStyle from "../validations/validation-icon.style"; -import ValidationIcon from "../validations/validation-icon.component"; -import Icon, { IconType } from "../../components/icon"; - -import InputIconToggle, { InputIconToggleProps } from "."; -import InputIconToggleStyle from "./input-icon-toggle.style"; - -function renderInputIconToggle(props: InputIconToggleProps) { - const defaultProps = { inputIcon: "settings" as IconType }; - - return ; -} - -describe("InputIconToggle", () => { - describe("tooltip positioning", () => { - const testCases: [InputIconToggleProps["align"], string][] = [ - ["left", "right"], - ["right", "left"], - ]; - it.each(testCases)( - "when align prop is passed as %s, tooltip position should be set as %s", - (tooltipAlign, tooltipPosition) => { - const wrapper = shallow( - renderInputIconToggle({ - align: tooltipAlign, - useValidationIcon: true, - error: "Message", - }) - ); - - expect(wrapper.find(ValidationIcon).props().tooltipPosition).toBe( - tooltipPosition - ); - } - ); - }); - - describe.each(["error", "warning", "info"])( - "when %s validation prop is set", - (validationProp) => { - describe("as a string and useValidationIcon is true", () => { - it("renders a validation icon", () => { - const wrapper = shallow( - renderInputIconToggle({ - [validationProp]: "Message", - useValidationIcon: true, - }) - ); - const validationIcon = wrapper.find(ValidationIcon); - expect(validationIcon.exists()).toBe(true); - }); - - describe("with disabled prop", () => { - it("does render an icon", () => { - const wrapper = mount( - renderInputIconToggle({ - [validationProp]: "Message", - useValidationIcon: true, - disabled: true, - }) - ); - expect(wrapper.find(ValidationIcon).exists()).toBe(false); - expect(wrapper.find(Icon).exists()).toBe(true); - }); - }); - - describe("with readonly prop", () => { - it("renders a validation icon", () => { - const wrapper = shallow( - renderInputIconToggle({ - [validationProp]: "Message", - useValidationIcon: true, - readOnly: true, - }) - ); - expect(wrapper.find(ValidationIcon).exists()).toBe(true); - expect(wrapper.find(Icon).exists()).toBe(false); - }); - }); - }); - - describe("as an empty string with useValidationIcon set to true", () => { - let wrapper: ReactWrapper; - - beforeAll(() => { - wrapper = mount( - renderInputIconToggle({ - inputIcon: "dropdown", - [validationProp]: "", - useValidationIcon: true, - }) - ); - }); - - it("does not render a validation icon", () => { - const validationIcon = wrapper.find(ValidationIcon); - expect(validationIcon.exists()).toBe(false); - }); - - it("renders input icon", () => { - expect(wrapper.find(Icon).props().type).toBe("dropdown"); - }); - }); - - describe("as a boolean with useValidationIcon set to true", () => { - let wrapper: ReactWrapper; - - beforeAll(() => { - wrapper = mount( - renderInputIconToggle({ - inputIcon: "dropdown", - [validationProp]: true, - useValidationIcon: true, - }) - ); - }); - - it("does not render a validation icon", () => { - const validationIcon = wrapper.find(ValidationIcon); - expect(validationIcon.exists()).toBe(false); - }); - - it("renders input icon", () => { - expect(wrapper.find(Icon).props().type).toBe("dropdown"); - }); - }); - - describe("as a string with useValidationIcon prop set to false", () => { - it("renders input icon instead of validation icon", () => { - const wrapper = mount( - renderInputIconToggle({ - inputIcon: "dropdown", - [validationProp]: "Message", - useValidationIcon: false, - }) - ); - expect(wrapper.find(ValidationIcon).exists()).toBe(false); - expect(wrapper.find(Icon).props().type).toBe("dropdown"); - }); - }); - } - ); - - describe("renders input icon", () => { - it("when disabled prop is true", () => { - const wrapper = mount( - renderInputIconToggle({ inputIcon: "dropdown", disabled: true }) - ); - expect(wrapper.find(Icon).exists()).toBe(true); - expect(wrapper.find(Icon).prop("disabled")).toBe(true); - assertStyleMatch( - { - cursor: "not-allowed", - }, - wrapper.find(InputIconToggleStyle) - ); - }); - it("when readOnly prop is true", () => { - const wrapper = mount( - renderInputIconToggle({ inputIcon: "dropdown", readOnly: true }) - ); - - expect(wrapper.find(Icon).exists()).toBe(true); - expect(wrapper.find(Icon).prop("disabled")).toBe(true); - assertStyleMatch( - { - cursor: "default", - }, - wrapper.find(InputIconToggleStyle) - ); - }); - }); - - describe.each([ - ["small", "var(--sizing400)"], - ["medium", "var(--sizing500)"], - ["large", "var(--sizing600)"], - ])("sizes", (iconSize, width) => { - it("updates the width for %s", () => { - assertStyleMatch( - { - width, - }, - mount( - renderInputIconToggle({ - size: iconSize as InputIconToggleProps["size"], - }) - ) - ); - }); - }); - - describe("default onKeydown handler", () => { - it.each([ - ["Enter", "Enter"], - ["Space", " "], - ])( - "prevents default when pressing `%s` and onClick is set", - (keyname, key) => { - const wrapper = mount( - renderInputIconToggle({ inputIcon: "dropdown", onClick: () => {} }) - ); - const event = { key, preventDefault: jest.fn() }; - act(() => { - wrapper.simulate("keydown", event); - }); - expect(event.preventDefault).toHaveBeenCalled(); - } - ); - - it("does not prevent default if onClick is not set", () => { - const wrapper = mount(renderInputIconToggle({ inputIcon: "dropdown" })); - const event = { key: " ", preventDefault: jest.fn() }; - act(() => { - wrapper.simulate("keydown", event); - }); - expect(event.preventDefault).not.toHaveBeenCalled(); - }); - }); - - describe("event handlers", () => { - describe("validation", () => { - it("onFocus", () => { - const mockOnFocus = jest.fn(); - const wrapper = mount( - renderInputIconToggle({ - error: "error", - onFocus: mockOnFocus, - useValidationIcon: true, - }) - ); - act(() => { - wrapper.find(ValidationIconStyle).props().onFocus(); - }); - expect(mockOnFocus).toHaveBeenCalled(); - }); - - it("onBlur", () => { - const mockOnBlur = jest.fn(); - const wrapper = mount( - renderInputIconToggle({ - error: "error", - onBlur: mockOnBlur, - useValidationIcon: true, - }) - ); - act(() => { - wrapper.find(ValidationIconStyle).props().onBlur(); - }); - expect(mockOnBlur).toHaveBeenCalled(); - }); - }); - - describe("basic", () => { - it("onFocus", () => { - const mockOnFocus = jest.fn(); - const wrapper = mount( - renderInputIconToggle({ inputIcon: "dropdown", onFocus: mockOnFocus }) - ); - act(() => { - wrapper.find(InputIconToggleStyle).props().onFocus(); - }); - expect(mockOnFocus).toHaveBeenCalled(); - }); - - it("onBlur", () => { - const mockOnBlur = jest.fn(); - const wrapper = mount( - renderInputIconToggle({ inputIcon: "dropdown", onBlur: mockOnBlur }) - ); - act(() => { - wrapper.find(InputIconToggleStyle).props().onBlur(); - }); - expect(mockOnBlur).toHaveBeenCalled(); - }); - }); - - describe("validationIconId", () => { - it("passes tooltipId to ValidationIcon", () => { - const validationIconId = "validation-id"; - const wrapper = mount( - renderInputIconToggle({ - error: "Error", - validationIconId, - useValidationIcon: true, - }) - ); - - expect(wrapper.find(ValidationIcon).props().tooltipId).toBe( - validationIconId - ); - }); - }); - }); -}); diff --git a/src/__internal__/input-icon-toggle/input-icon-toggle.test.tsx b/src/__internal__/input-icon-toggle/input-icon-toggle.test.tsx new file mode 100644 index 0000000000..86b865e4bc --- /dev/null +++ b/src/__internal__/input-icon-toggle/input-icon-toggle.test.tsx @@ -0,0 +1,358 @@ +import React from "react"; +import { render, screen, createEvent, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import * as floatingUi from "@floating-ui/react-dom"; +import InputIconToggle, { InputIconToggleProps } from "."; + +test.each(["error", "warning", "info"])( + "renders only a validation icon when the validation prop is set to %s as a string and `useValidationIcon` is true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.getByTestId(`icon-${validationProp}`); + const icon = screen.queryByTestId("icon"); + expect(validationIcon).toBeVisible(); + expect(icon).not.toBeInTheDocument(); + } +); + +test.each([ + ["left", "right"], + ["right", "left"], +] as [InputIconToggleProps["align"], string][])( + "when the align prop is passed as %s, the floating UI tooltip position should be set as %s", + (tooltipAlign, tooltipPosition) => { + const useFloatingSpy = jest.spyOn(floatingUi, "useFloating"); + + render( + + ); + + expect(useFloatingSpy).toHaveBeenCalledWith( + expect.objectContaining({ placement: tooltipPosition }) + ); + + useFloatingSpy.mockRestore(); + } +); + +test.each(["error", "warning", "info"])( + "renders only a validation icon when the validation prop is set to %s as a string and both `useValidationIcon` and `readOnly` props are true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.getByTestId(`icon-${validationProp}`); + const icon = screen.queryByTestId("icon"); + expect(validationIcon).toBeVisible(); + expect(icon).not.toBeInTheDocument(); + } +); + +test.each(["error", "warning", "info"])( + "renders only an input icon when the validation prop is set to %s as a string and both `useValidationIcon` and `disabled` props are true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders only an input icon when the validation prop is set to %s as an empty string and `useValidationIcon` is true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders only an input icon when the validation prop is set to %s as a boolean and `useValidationIcon` is true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders only an input icon when the validation prop is set to %s as a string and `useValidationIcon` is false", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders an input icon, when the %s prop is null and `useValidationIcon` is true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders an input icon, when the %s prop is undefined and `useValidationIcon` is true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test("renders an input icon, when `disabled` prop is true, and no validation prop nor `useValidationIcon` is set", () => { + render(); + + const icon = screen.getByTestId("icon"); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); +}); + +test("renders only an input icon when the `readOnly` prop is true and no `validationProp` or `useValidationIcon` is set", () => { + render(); + + const icon = screen.getByTestId("icon"); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); +}); + +test("renders only an input icon when both `readOnly` and `disabled` props are true and `useValidationIcon` is false", () => { + render( + + ); + + const icon = screen.getByTestId("icon"); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); +}); + +test.each(["error", "warning", "info"])( + "renders only an input icon when both `readOnly` and `disabled` props are true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each(["error", "warning", "info"])( + "renders only an input icon when `useValidationIcon`, `readOnly`, and `disabled` props are true", + (validationProp) => { + render( + + ); + + const validationIcon = screen.queryByTestId(`icon-${validationProp}`); + const icon = screen.getByTestId("icon"); + expect(validationIcon).not.toBeInTheDocument(); + expect(icon).toBeVisible(); + expect(icon).toHaveAttribute("type", "settings"); + } +); + +test.each([ + ["Enter", "Enter"], + ["Space", " "], +])( + "prevents default action when pressing `%s` and `onClick` is set", + async (keyname, key) => { + render( {}} />); + + const icon = screen.getByTestId("icon"); + const keydownEvent = createEvent.keyDown(icon, { key }); + + fireEvent(icon, keydownEvent); + + expect(keydownEvent.defaultPrevented).toBeTruthy(); + } +); + +test.each([ + ["Enter", "Enter"], + ["Space", " "], +])( + "does not prevent default action when pressing `%s` and `onClick` is not set", + (keyname, key) => { + render(); + + const icon = screen.getByTestId("icon"); + const keydownEvent = createEvent.keyDown(icon, { key }); + + fireEvent(icon, keydownEvent); + + expect(keydownEvent.defaultPrevented).toBeFalsy(); + } +); + +test("calls `onFocus` handler when the validation icon is focused", async () => { + const mockOnFocus = jest.fn(); + render( + + ); + + const validationIcon = screen.getByTestId("icon-error"); + validationIcon.focus(); + + expect(mockOnFocus).toHaveBeenCalled(); +}); + +test("calls `onBlur` handler when the validation icon loses focus", async () => { + const mockOnBlur = jest.fn(); + render( + + ); + + const validationIcon = screen.getByTestId("icon-error"); + const user = userEvent.setup(); + + validationIcon.focus(); + await user.tab(); + + expect(mockOnBlur).toHaveBeenCalled(); +}); + +test("passes `validationIconId` to the tooltip when hovering over the validation icon", async () => { + const validationIconId = "validation-id"; + + render( + + ); + + const user = userEvent.setup(); + const validationIcon = screen.getByTestId("icon-error"); + + await user.hover(validationIcon); + const tooltip = screen.getByRole("tooltip"); + + expect(tooltip).toHaveAttribute("id", validationIconId); +}); + +test("renders no icons when `useValidationIcon`, `inputIcon`, and `validationProp` are all undefined", () => { + render(); + + const anyIcon = screen.queryByTestId(/^icon/); + expect(anyIcon).not.toBeInTheDocument(); +});