diff --git a/src/dropdown/list/DropdownList.tsx b/src/dropdown/list/DropdownList.tsx index b99e5722..95158516 100644 --- a/src/dropdown/list/DropdownList.tsx +++ b/src/dropdown/list/DropdownList.tsx @@ -10,7 +10,7 @@ import DropdownListItem, { } from "./item/DropdownListItem"; import {computeScrollAmountToMakeChildVisible} from "../../core/utils/dom/domUtils"; -interface DropdownListProps { +export interface DropdownListProps { isVisible: boolean; options: DropdownOption[]; selectedOption: DropdownSelectedOption; diff --git a/src/dropdown/list/dropdown-list.test.tsx b/src/dropdown/list/dropdown-list.test.tsx new file mode 100644 index 00000000..fd913ee3 --- /dev/null +++ b/src/dropdown/list/dropdown-list.test.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import {render, screen} from "@testing-library/react"; +import "@testing-library/jest-dom"; + +import {testA11y} from "../../core/utils/test/testUtils"; +import DropdownList, {DropdownListProps} from "./DropdownList"; + +describe("", () => { + const defaultDropdownListProps: DropdownListProps = { + testid: "dropdown-list", + options: [ + {id: "1", title: "dropdown-option-1"}, + {id: "2", title: "dropdown-option-2"}, + {id: "3", title: "dropdown-option-3"} + ], + focusedOption: {id: "1", title: "test"}, + isVisible: true, + onFocus: jest.fn(), + onSelect: jest.fn(), + selectedOption: {id: "1", title: "test"}, + role: "listbox" + }; + + it("should render correctly", () => { + render(); + }); + + it("should pass a11y test", async () => { + const {container} = render(); + + await testA11y(container, { + rules: { + "aria-required-parent": {enabled: false}, + list: {enabled: false}, + "aria-input-field-name": {enabled: false} + } + }); + }); + + it("should render noOptionsMessage if options are empty", () => { + render( + + ); + + expect(screen.getByRole("listbox")).toContainElement(screen.getByText("No options")); + }); +}); diff --git a/src/dropdown/list/item/DropdownListItem.tsx b/src/dropdown/list/item/DropdownListItem.tsx index 8412beea..c27621c4 100644 --- a/src/dropdown/list/item/DropdownListItem.tsx +++ b/src/dropdown/list/item/DropdownListItem.tsx @@ -24,7 +24,7 @@ export type DropdownSelectedOption = | null | undefined; -interface DropdownListItemProps { +export interface DropdownListItemProps { testid?: string; option: DropdownOption; selectedOption: DropdownSelectedOption; diff --git a/src/dropdown/list/item/dropdown-list-item.test.tsx b/src/dropdown/list/item/dropdown-list-item.test.tsx new file mode 100644 index 00000000..96950748 --- /dev/null +++ b/src/dropdown/list/item/dropdown-list-item.test.tsx @@ -0,0 +1,147 @@ +import React from "react"; +import {render, screen, fireEvent} from "@testing-library/react"; +import "@testing-library/jest-dom"; +import userEvent from "@testing-library/user-event"; + +import {testA11y} from "../../../core/utils/test/testUtils"; +import DropdownListItem, {DropdownListItemProps} from "./DropdownListItem"; + +describe("", () => { + const defaultDropdownListItemProps: DropdownListItemProps = { + testid: "dropdown-list-item", + selectedOption: {id: "1", title: "test"}, + option: {id: "1", title: "test"}, + onFocus: jest.fn(), + onSelect: jest.fn(), + onKeyDown: jest.fn() + }; + + it("should render correctly", () => { + render(); + }); + + it("should pass a11y test", async () => { + const {container} = render(); + + await testA11y(container, {rules: {"aria-required-parent": {enabled: false}}}); + }); + + it("should render custom content if it exists", () => { + const {container} = render( + {"custom content"}

+ }} + /> + ); + + expect(container).toContainElement(screen.getByText("custom content")); + }); + + describe("should handle event handlers correctly", () => { + it("should not run click event handler when option is disabled and option is selected or canSelectAlreadySelected is provided as false", () => { + // isDisabled: true, isSelected: true , canSelectAlreadySelected: default = false + const {rerender} = render( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).not.toHaveBeenCalled(); + + // isDisabled: true, isSelected: false , canSelectAlreadySelected: default = false + rerender( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).not.toHaveBeenCalled(); + + // isDisabled: true, isSelected: true , canSelectAlreadySelected: true + rerender( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).not.toHaveBeenCalled(); + }); + + it("should run click event handler when option is not disabled and option is not selected or canSelectAlreadySelected is provided as true", () => { + // isDisabled: false, isSelected: true , canSelectAlreadySelected: true + const {rerender} = render( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).toHaveBeenCalledTimes(1); + + jest.clearAllMocks(); + + // isDisabled: false, isSelected: false , canSelectAlreadySelected: default = false + rerender( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).toHaveBeenCalledTimes(1); + + jest.clearAllMocks(); + + // isDisabled: false, isSelected: false , canSelectAlreadySelected: true + rerender( + + ); + + userEvent.click(screen.getByRole("option")); + expect(defaultDropdownListItemProps.onSelect).toHaveBeenCalledTimes(1); + }); + + it("should run focus event handler correctly", () => { + render(); + + fireEvent.focus(screen.getByRole("option")); + + expect(defaultDropdownListItemProps.onFocus).toHaveBeenCalledTimes(1); + }); + + it("should run key down event handler correctly", () => { + const {rerender} = render(); + + fireEvent.keyDown(screen.getByRole("option"), { + keyCode: 16 + }); + + expect(defaultDropdownListItemProps.onKeyDown).toHaveBeenCalledTimes(1); + + jest.clearAllMocks(); + + rerender( + + ); + + expect(defaultDropdownListItemProps.onKeyDown).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/dropdown/util/dropdown-utils.test.ts b/src/dropdown/util/dropdown-utils.test.ts new file mode 100644 index 00000000..e13dd22b --- /dev/null +++ b/src/dropdown/util/dropdown-utils.test.ts @@ -0,0 +1,85 @@ +import {DropdownOption} from "../list/item/DropdownListItem"; +import { + findIndexForClosestMatch, + generateInitialFocusedDropdownOptionIndex +} from "./dropdownUtils"; +import {DropdownPosition} from "./dropdownConstants"; + +describe("generateInitialFocusedDropdownOptionIndex", () => { + const position: DropdownPosition = "left"; + const options: DropdownOption[] = [ + {id: "1", title: "Hipo"}, + {id: "2", title: "hIpO lAbS"}, + {id: "3", title: "lAbS"}, + {id: "4", title: "Labss"}, + {id: "5", title: ""}, + {id: "6", title: "hİpOğÜ_*!>labs"} + ]; + + it("should return provided selectedOption's index if it exists", () => { + expect( + generateInitialFocusedDropdownOptionIndex(position, options, options[1]) + ).toEqual(1); + }); + + it("should return 0 when provided selectedOption does not exists and position is not equal to top", () => { + expect( + generateInitialFocusedDropdownOptionIndex(position, options, { + id: "-1", + title: "test" + }) + ).toEqual(0); + + expect(generateInitialFocusedDropdownOptionIndex(position, options, null)).toEqual(0); + expect( + generateInitialFocusedDropdownOptionIndex(position, options, undefined) + ).toEqual(0); + }); + + it("should return last option's index when selectedOption does not exists and position is equal to top", () => { + expect( + generateInitialFocusedDropdownOptionIndex("top", options, { + id: "-1", + title: "test" + }) + ).toEqual(options.length - 1); + }); + + expect(generateInitialFocusedDropdownOptionIndex("top", options, null)).toEqual( + options.length - 1 + ); + expect(generateInitialFocusedDropdownOptionIndex("top", options, undefined)).toEqual( + options.length - 1 + ); +}); + +describe("findIndexForClosestMatch", () => { + const options: DropdownOption[] = [ + {id: "1", title: "Hipo"}, + {id: "2", title: "hIpO lAbS"}, + {id: "3", title: "lAbS"}, + {id: "4", title: "Labss"}, + {id: "5", title: ""}, + {id: "6", title: "hİpOğÜ_*!>labs"} + ]; + + it("should return closest index by provided query", () => { + expect(findIndexForClosestMatch(options, "HipO")).toEqual(0); + // eslint-disable-next-line no-magic-numbers + expect(findIndexForClosestMatch(options, "labs")).toEqual(2); + expect(findIndexForClosestMatch(options, "hipo la")).toEqual(1); + }); + + it("should return 0 if the provided query is empty", () => { + expect(findIndexForClosestMatch(options, "")).toEqual(0); + }); + + it("should return -1 if the provided query does not exists", () => { + expect(findIndexForClosestMatch(options, "test")).toEqual(-1); + }); + + it("should handle special characters", () => { + // eslint-disable-next-line no-magic-numbers + expect(findIndexForClosestMatch(options, "hİpOğÜ_*!>")).toEqual(5); + }); +});