diff --git a/src/components/select/__internal__/select-list/select-list.test.tsx b/src/components/select/__internal__/select-list/select-list.test.tsx
index 8b5270223a..be9a49d349 100644
--- a/src/components/select/__internal__/select-list/select-list.test.tsx
+++ b/src/components/select/__internal__/select-list/select-list.test.tsx
@@ -109,23 +109,26 @@ describe("rendered content", () => {
).toBeVisible();
});
- it("renders list with correct positioning", async () => {
- const { rerender } = render(
-
-
-
- );
- rerender(
-
-
-
- );
+ it.each(["top", "bottom", "left", "right"] as const)(
+ "computes correct position for list when listPlacement prop is %s",
+ async (listPlacement) => {
+ const { rerender } = render(
+
+
+
+ );
+ rerender(
+
+
+
+ );
- expect(await screen.findByTestId("select-list-wrapper")).toHaveAttribute(
- "data-floating-placement",
- "bottom"
- );
- });
+ expect(await screen.findByTestId("select-list-wrapper")).toHaveAttribute(
+ "data-floating-placement",
+ listPlacement
+ );
+ }
+ );
it("highlights the correct selected option when highlightedValue prop is provided", () => {
render(
diff --git a/src/components/select/simple-select/simple-select.component.tsx b/src/components/select/simple-select/simple-select.component.tsx
index 90bf3b2da5..2558006423 100644
--- a/src/components/select/simple-select/simple-select.component.tsx
+++ b/src/components/select/simple-select/simple-select.component.tsx
@@ -24,8 +24,6 @@ import useInputAccessibility from "../../../hooks/__internal__/useInputAccessibi
let deprecateUncontrolledWarnTriggered = false;
-type TimerId = ReturnType;
-
export interface OptionData {
text?: string;
value?: string | Record;
@@ -146,7 +144,7 @@ export const SimpleSelect = React.forwardRef<
const selectListId = useRef(guid());
const containerRef = useRef(null);
const listboxRef = useRef(null);
- const filterTimer = useRef();
+ const filterTimer = useRef(undefined);
const isMouseDownReported = useRef();
const isControlled = useRef(value !== undefined);
const isTimerCounting = useRef();
@@ -164,7 +162,7 @@ export const SimpleSelect = React.forwardRef<
id: inputId.current,
label,
});
- const focusTimer = useRef>(null);
+ const focusTimer = useRef(undefined);
const componentIsUncontrolled =
!isControlled || (!onChange && defaultValue);
@@ -214,9 +212,7 @@ export const SimpleSelect = React.forwardRef<
return previousValue;
}
- if (onChange) {
- onChange(createCustomEvent(match.props.value));
- }
+ onChange?.(createCustomEvent(match.props.value));
if (isControlled.current) {
return previousValue;
@@ -237,16 +233,16 @@ export const SimpleSelect = React.forwardRef<
filterText.current = newVal;
selectValueStartingWithText(newVal);
- clearTimeout(filterTimer.current as TimerId);
+ window.clearTimeout(filterTimer.current);
} else {
filterText.current = newCharacter;
selectValueStartingWithText(newCharacter);
}
isTimerCounting.current = true;
- clearTimeout(filterTimer.current as TimerId);
+ window.clearTimeout(filterTimer.current);
- filterTimer.current = setTimeout(() => {
+ filterTimer.current = window.setTimeout(() => {
isTimerCounting.current = false;
filterText.current = "";
}, 500);
@@ -258,21 +254,15 @@ export const SimpleSelect = React.forwardRef<
(event) => {
const { key } = event;
- if (onKeyDown) {
- onKeyDown(event);
- }
+ onKeyDown?.(event);
- if (readOnly) {
- return;
- }
+ if (readOnly) return;
if (key === " " || isNavigationKey(key)) {
event.preventDefault();
setOpenState((isAlreadyOpen) => {
- if (!isAlreadyOpen && onOpen) {
- onOpen();
- }
+ if (!isAlreadyOpen) onOpen?.();
return true;
});
@@ -344,25 +334,22 @@ export const SimpleSelect = React.forwardRef<
useEffect(() => {
return function cleanup() {
- clearTimeout(filterTimer.current as TimerId);
+ window.clearTimeout(filterTimer.current);
+ window.clearTimeout(focusTimer.current);
};
}, []);
function handleTextboxClick(event: React.MouseEvent) {
isMouseDownReported.current = false;
- if (onClick) {
- onClick(event);
- }
+ onClick?.(event);
setOpenState((isAlreadyOpen) => {
if (isAlreadyOpen) {
return false;
}
- if (onOpen) {
- onOpen();
- }
+ onOpen?.();
return true;
});
@@ -383,9 +370,7 @@ export const SimpleSelect = React.forwardRef<
return;
}
- if (onBlur) {
- onBlur(event);
- }
+ onBlur?.(event);
}
function handleTextboxMouseDown() {
@@ -397,9 +382,7 @@ export const SimpleSelect = React.forwardRef<
return;
}
- if (onFocus) {
- onFocus(event);
- }
+ onFocus?.(event);
if (isMouseDownReported.current) {
isMouseDownReported.current = false;
@@ -408,21 +391,17 @@ export const SimpleSelect = React.forwardRef<
}
if (openOnFocus) {
- if (focusTimer.current) {
- clearTimeout(focusTimer.current);
- }
+ window.clearTimeout(focusTimer.current);
// we need to use a timeout here as there is a race condition when rendered in a modal
// whereby the select list isn't visible when the select is auto focused straight away
- focusTimer.current = setTimeout(() => {
+ focusTimer.current = window.setTimeout(() => {
setOpenState((isAlreadyOpen) => {
if (isAlreadyOpen) {
return true;
}
- if (onOpen) {
- onOpen();
- }
+ onOpen?.();
return true;
});
@@ -440,9 +419,7 @@ export const SimpleSelect = React.forwardRef<
setTextValue(text);
}
- if (onChange) {
- onChange(createCustomEvent(newValue, selectionConfirmed));
- }
+ onChange?.(createCustomEvent(newValue, selectionConfirmed));
}
function onSelectOption(optionData: OptionData) {
diff --git a/src/components/select/simple-select/simple-select.spec.tsx b/src/components/select/simple-select/simple-select.spec.tsx
deleted file mode 100644
index a819ea688a..0000000000
--- a/src/components/select/simple-select/simple-select.spec.tsx
+++ /dev/null
@@ -1,1061 +0,0 @@
-import React, { useRef } from "react";
-import { act } from "react-dom/test-utils";
-import { mount, ReactWrapper } from "enzyme";
-import StyledLabel, {
- StyledLabelContainer,
-} from "../../../__internal__/label/label.style";
-
-import {
- assertStyleMatch,
- testStyledSystemMargin,
-} from "../../../__spec_helper__/__internal__/test-utils";
-import {
- simulateSelectTextboxEvent,
- simulateDropdownEvent,
-} from "../../../__spec_helper__/__internal__/select-test-utils";
-import { Select as SimpleSelect, Option, SimpleSelectProps } from "..";
-import Textbox from "../../textbox";
-import SelectList from "../__internal__/select-list/select-list.component";
-import {
- StyledSelectListContainer,
- StyledScrollableContainer,
-} from "../__internal__/select-list/select-list.style";
-import InputIconToggleStyle from "../../../__internal__/input-icon-toggle/input-icon-toggle.style";
-import InputPresentationStyle from "../../../__internal__/input/input-presentation.style";
-import { InputPresentation } from "../../../__internal__/input";
-import Logger from "../../../__internal__/utils/logger";
-import guid from "../../../__internal__/utils/helpers/guid";
-import StyledInput from "../../../__internal__/input/input.style";
-import SelectTextbox from "../__internal__/select-textbox";
-import mockDOMRect from "../../../__spec_helper__/mock-dom-rect";
-
-const mockedGuid = "mocked-guid";
-jest.mock("../../../__internal__/utils/helpers/guid");
-jest.useFakeTimers();
-(guid as jest.MockedFunction).mockReturnValue(mockedGuid);
-
-function getSelect(props: Partial = {}) {
- return (
-
-
-
-
-
-
- );
-}
-
-function renderSelect(props = {}, renderer = mount, opts = {}) {
- return renderer(getSelect(props), {
- attachTo: document.getElementById("enzymeContainer"),
- ...opts,
- });
-}
-
-function simulateKeyDown(
- container: ReactWrapper,
- key: string,
- options: Partial = {}
-) {
- const selectText = container.find(SelectTextbox).first();
-
- act(() => {
- selectText.prop("onKeyDown")?.({
- key,
- preventDefault: () => {},
- ...options,
- } as React.KeyboardEvent);
- });
-}
-
-jest.mock("../../../__internal__/utils/logger");
-
-describe("SimpleSelect", () => {
- let loggerSpy: jest.SpyInstance | jest.Mock;
- let container: HTMLDivElement | null;
-
- beforeEach(() => {
- container = document.createElement("div");
- container.id = "enzymeContainer";
- document.body.appendChild(container);
- });
-
- afterEach(() => {
- if (container && container.parentNode) {
- container.parentNode.removeChild(container);
- }
-
- container = null;
- });
-
- beforeEach(() => {
- mockDOMRect(200, 200, "select-list-scrollable-container");
- });
-
- describe("Deprecation warning for uncontrolled", () => {
- beforeEach(() => {
- loggerSpy = jest.spyOn(Logger, "deprecate");
- });
-
- afterEach(() => {
- loggerSpy.mockRestore();
- });
-
- afterAll(() => {
- loggerSpy.mockClear();
- });
-
- it("should display deprecation warning once", () => {
- renderSelect({ defaultValue: "opt1" });
-
- expect(loggerSpy).toHaveBeenCalledWith(
- "Uncontrolled behaviour in `Simple Select` is deprecated and support will soon be removed. Please make sure all your inputs are controlled."
- );
-
- expect(loggerSpy).toHaveBeenCalledTimes(1);
- });
- });
-
- describe("when the id prop is set", () => {
- const mockId = "foo";
- const wrapper = renderSelect({ id: mockId, label: "bar" });
-
- it("then it should be passed to the Textbox component", () => {
- expect(wrapper.find(Textbox).prop("id")).toBe(mockId);
- });
-
- it("then a label id based on that prop should be passed to the SelectList component", () => {
- expect(wrapper.find(SelectList).prop("labelId")).toBe(`${mockId}-label`);
- });
-
- it("then a label id based on that prop should be passed to the Textbox component", () => {
- expect(wrapper.find(Textbox).prop("labelId")).toBe(`${mockId}-label`);
- });
- });
-
- describe("when the id prop is not set", () => {
- const wrapper = renderSelect({ id: undefined, label: "bar" });
-
- it("then a randomly generated id should be passed to the Textbox component", () => {
- expect(wrapper.find(Textbox).prop("id")).toBe(mockedGuid);
- });
-
- it("then a label id based on randomly generated id should be passed to the SelectList component", () => {
- expect(wrapper.find(SelectList).prop("labelId")).toBe(
- `${mockedGuid}-label`
- );
- });
-
- it("then a label id based on a randomly generated id should be passed to the Textbox component", () => {
- expect(wrapper.find(Textbox).prop("labelId")).toBe(`${mockedGuid}-label`);
- });
- });
-
- describe("when an HTML element is clicked when the SelectList is open", () => {
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- wrapper = renderSelect();
- });
-
- describe("and that element is an Option of the Select List", () => {
- it("then the SelectList should be closed", () => {
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- act(() => {
- wrapper
- .find(Option)
- .first()
- .getDOMNode()
- .dispatchEvent(new MouseEvent("click", { bubbles: true }));
- });
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
-
- describe("and that element is not part of the Select", () => {
- it("then the SelectList should be closed", () => {
- act(() => {
- document.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- });
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- act(() => {
- document.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- });
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
- });
-
- testStyledSystemMargin((props) => getSelect(props));
-
- it("when placeholder prop is passed, textbox uses it as placeholder text", () => {
- const placeholder = "foobaz";
- const wrapper = renderSelect({ placeholder });
- expect(wrapper.find("span[data-element='select-text']").text()).toBe(
- placeholder
- );
- });
-
- describe("with a ref", () => {
- it("the input ref should be forwarded", () => {
- const getNode: React.RefCallback = jest.fn(
- (element) => element
- );
-
- const WrapperComponent = () => (
-
-
-
-
-
-
- );
-
- const wrapper = mount();
-
- expect(getNode).toBeCalledWith(wrapper.find("input").getDOMNode());
- });
-
- it("the input callback ref should be called with the DOM element", () => {
- let mockRef;
-
- const WrapperComponent = () => {
- mockRef = jest.fn();
-
- return (
-
-
-
-
-
-
- );
- };
-
- const wrapper = mount();
-
- expect(mockRef).toHaveBeenCalledWith(wrapper.find("input").getDOMNode());
- });
- });
-
- it("the input toggle icon should have proper left margin", () => {
- const wrapper = renderSelect();
-
- assertStyleMatch(
- {
- marginRight: "0",
- },
- wrapper,
- { modifier: `${InputIconToggleStyle}` }
- );
- });
-
- describe("when listMaxHeight prop is provided", () => {
- it("overrides default list max-height", () => {
- const wrapper = renderSelect({ listMaxHeight: 120, openOnFocus: true });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- assertStyleMatch(
- { maxHeight: "120px" },
- wrapper.find(StyledScrollableContainer)
- );
- });
- });
-
- describe("when the transparent prop is set to true", () => {
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- wrapper = renderSelect({ transparent: true });
- });
-
- it("then the input should have transparent background and no border", () => {
- assertStyleMatch(
- {
- background: "transparent",
- border: "none",
- },
- wrapper,
- { modifier: `${InputPresentationStyle}` }
- );
- });
- });
-
- describe("when the value prop is passed", () => {
- it("then the formatted value should be set to corresponding option text", () => {
- const wrapper = renderSelect({ value: "opt2", onChange: jest.fn() });
-
- expect(wrapper.find(Textbox).prop("formattedValue")).toBe("green");
- });
- });
-
- describe("when the openOnFocus prop is set", () => {
- describe("and the Textbox Input is focused", () => {
- it("the SelectList should be rendered", () => {
- const wrapper = renderSelect({ openOnFocus: true });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- });
-
- describe.each(["readOnly", "disabled"])(
- "with the %s prop passed",
- (prop) => {
- it("the SelectList should not be rendered", () => {
- const obj = { [prop]: true, openOnFocus: true };
- const wrapper = renderSelect(obj);
-
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- }
- );
-
- describe("with the onFocus prop passed", () => {
- it("then that prop should be called", () => {
- const onFocusFn = jest.fn();
- const wrapper = renderSelect({
- onFocus: onFocusFn,
- openOnFocus: true,
- });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(onFocusFn).toHaveBeenCalled();
- });
- });
-
- describe("with the onOpen prop passed", () => {
- let wrapper: ReactWrapper;
- let onOpenFn: jest.Mock;
-
- beforeEach(() => {
- onOpenFn = jest.fn();
- wrapper = renderSelect({ onOpen: onOpenFn, openOnFocus: true });
- });
-
- it("then that prop should be called", () => {
- simulateSelectTextboxEvent(wrapper, "focus");
-
- expect(onOpenFn).toHaveBeenCalled();
- });
-
- describe("and the SelectList already open", () => {
- it("then that prop should not be called", () => {
- simulateSelectTextboxEvent(wrapper, "focus");
- onOpenFn.mockReset();
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(onOpenFn).not.toHaveBeenCalled();
- });
- });
-
- describe("and the focus triggered by mouseDown", () => {
- it("then that prop should not be called", () => {
- simulateSelectTextboxEvent(wrapper, "mousedown");
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(onOpenFn).not.toHaveBeenCalled();
- });
- });
- });
- });
- });
-
- describe("when the Textbox Input is focused", () => {
- let onOpenFn: jest.Mock;
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- onOpenFn = jest.fn();
- wrapper = renderSelect({ onOpen: onOpenFn });
- });
-
- it("the SelectList should not be rendered", () => {
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
-
- describe.each([
- "ArrowDown",
- "ArrowUp",
- "Home",
- "End",
- " ", // spacebar
- ])('and the "%s" key is pressed', (key) => {
- it("the SelectList should be rendered", () => {
- simulateKeyDown(wrapper, key);
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- });
-
- it("the onOpen prop should be called", () => {
- simulateKeyDown(wrapper, key);
- expect(onOpenFn).toHaveBeenCalled();
- });
-
- describe("with the SelectList already open", () => {
- it("the onOpen prop should not be called", () => {
- simulateSelectTextboxEvent(wrapper, "click");
- onOpenFn.mockReset();
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- simulateKeyDown(wrapper, key);
- expect(onOpenFn).not.toHaveBeenCalled();
- });
- });
-
- describe("with readOnly prop set to true", () => {
- it("then the SelectList should not be rendered", () => {
- wrapper.setProps({ readOnly: true });
- simulateKeyDown(wrapper, key);
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
- });
-
- describe("and a key other than Enter, Up or Down is pressed", () => {
- it("the SelectList should not be rendered", () => {
- simulateKeyDown(wrapper, "b");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
-
- describe("with readOnly prop set to true", () => {
- it("then the SelectList should not be rendered", () => {
- wrapper.setProps({ readOnly: true });
- wrapper.update();
- simulateKeyDown(wrapper, "b");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
- });
- });
-
- describe("when the Textbox Input is clicked", () => {
- it("the SelectList should be rendered", () => {
- const wrapper = renderSelect();
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- });
-
- describe.each(["disabled", "readOnly"])(
- "and the %s prop is set to true",
- (prop) => {
- it('then the "onClick" prop should not be called', () => {
- const onClickFn = jest.fn();
- const wrapper = renderSelect({ onClick: onClickFn, [prop]: true });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(onClickFn).not.toHaveBeenCalled();
- });
-
- it("then the SelectList should not be rendered", () => {
- const wrapper = renderSelect({ [prop]: true });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- }
- );
-
- describe("and the onClick prop is passed", () => {
- it("then that prop should be called", () => {
- const onClickFn = jest.fn();
- const wrapper = renderSelect({ onClick: onClickFn });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(onClickFn).toHaveBeenCalled();
- });
- });
-
- describe("and the onOpen prop is passed", () => {
- it("then that prop should be called", () => {
- const onOpenFn = jest.fn();
- const wrapper = renderSelect({ onOpen: onOpenFn });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(onOpenFn).toHaveBeenCalled();
- });
- });
-
- describe("and the SelectList is open", () => {
- it("then the SelectList should be closed", () => {
- const wrapper = renderSelect();
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
-
- it.each(["top", "bottom", "right", "left"])(
- "the listPlacement prop should be passed",
- (listPlacement) => {
- const wrapper = renderSelect({ listPlacement });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(wrapper.find(SelectList).prop("listPlacement")).toBe(
- listPlacement
- );
- }
- );
-
- it("the flipEnabled prop should be passed", () => {
- const wrapper = renderSelect({ flipEnabled: false });
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(wrapper.find(SelectList).prop("flipEnabled")).toBe(false);
- wrapper.setProps({ flipEnabled: true });
- expect(wrapper.find(SelectList).prop("flipEnabled")).toBe(true);
- });
- });
-
- describe("when the Dropdown Icon in the Textbox has been clicked", () => {
- it("the SelectList should be rendered", () => {
- const wrapper = renderSelect();
-
- simulateDropdownEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- });
-
- describe("and the SelectList is open", () => {
- it("then the SelectList should be closed", () => {
- const wrapper = renderSelect();
-
- simulateDropdownEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- simulateDropdownEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
- });
-
- describe("when a printable character key has been pressed in the Textbox", () => {
- it("then the first option with text starting with that character should be selected", () => {
- const wrapper = renderSelect();
-
- simulateKeyDown(wrapper, "b");
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("opt3");
- wrapper.unmount();
- });
-
- it("if the Meta key is pressed at the same time, nothing should happen", () => {
- const wrapper = renderSelect();
-
- simulateKeyDown(wrapper, "b", { metaKey: true });
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("");
- wrapper.unmount();
- });
-
- it("if the Control key is pressed at the same time, nothing should happen", () => {
- const wrapper = renderSelect();
-
- simulateKeyDown(wrapper, "b", { ctrlKey: true });
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("");
- wrapper.unmount();
- });
-
- describe("and the same key is pressed in a short amount of time", () => {
- it("then the second option with text starting with that character should be selected", () => {
- const wrapper = renderSelect();
-
- simulateKeyDown(wrapper, "b");
- simulateKeyDown(wrapper, "b");
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("opt4");
- wrapper.unmount();
- });
- });
-
- describe("and other key that does not match the text in any option has been typed", () => {
- it("then the option starting with previous character should remain selected", () => {
- const wrapper = renderSelect();
-
- simulateKeyDown(wrapper, "b");
- simulateKeyDown(wrapper, "x");
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("opt3");
- wrapper.unmount();
- });
- });
-
- describe("and another keys are typed in a short amount of time", () => {
- it("then an option with matching text should be selected", () => {
- const wrapper = renderSelect({ openOnFocus: true });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- simulateKeyDown(wrapper, "b");
- simulateKeyDown(wrapper, "l");
- simulateKeyDown(wrapper, "a");
- wrapper.update();
- expect(wrapper.find(Textbox).prop("value")).toBe("opt4");
- wrapper.unmount();
- });
- });
-
- describe("and another keys are typed with a long break before the last change", () => {
- it("then the first option with text starting the last typed character should be selected", () => {
- const wrapper = renderSelect();
-
- act(() => {
- simulateSelectTextboxEvent(wrapper, "focus");
- simulateKeyDown(wrapper, "b");
- simulateKeyDown(wrapper, "l");
- jest.runOnlyPendingTimers();
- simulateKeyDown(wrapper, "g");
- });
-
- expect(wrapper.update().find(Textbox).prop("value")).toBe("opt2");
- });
- });
-
- describe("and the onChange prop is passed", () => {
- it("then that prop should be called with the value of first matching option", () => {
- const textboxProps = {
- name: "testName",
- id: "testId",
- };
- const mockEventObject = {
- selectionConfirmed: false,
- target: {
- ...textboxProps,
- value: "opt3",
- },
- };
- const onChangeFn = jest.fn();
- const wrapper = renderSelect({ ...textboxProps, onChange: onChangeFn });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- simulateKeyDown(wrapper, "b");
- expect(onChangeFn).toHaveBeenCalledWith(mockEventObject);
- });
- });
- });
-
- describe("when the onSelect is called in the SelectList", () => {
- const navigationKeyOptionObject = {
- value: "opt2",
- text: "green",
- selectionType: "navigationKey",
- selectionConfirmed: false,
- };
- const clickOptionObject = {
- value: "opt2",
- text: "green",
- selectionType: "click",
- selectionConfirmed: true,
- };
- const textboxProps = {
- name: "testName",
- id: "testId",
- };
- const expectedEventObject = {
- selectionConfirmed: true,
- target: {
- ...textboxProps,
- value: "opt2",
- },
- };
-
- describe('with "selectionType" as "click"', () => {
- it("the SelectList should be closed", () => {
- const wrapper = renderSelect();
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- act(() => {
- wrapper.find(SelectList).prop("onSelect")(clickOptionObject);
- });
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
-
- describe('with "selectionType" as "navigationKey"', () => {
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- wrapper = renderSelect();
- simulateSelectTextboxEvent(wrapper, "click");
- act(() => {
- wrapper.find(SelectList).prop("onSelect")(navigationKeyOptionObject);
- });
- wrapper.update();
- });
-
- it("the SelectList should be open", () => {
- wrapper
- .find(Option)
- .forEach((option) => expect(option.getDOMNode()).toBeVisible());
- });
-
- it("the expected value should be selected", () => {
- expect(wrapper.find(Textbox).prop("value")).toBe(
- navigationKeyOptionObject.value
- );
- });
-
- it("the expected text should be displayed in the Textbox", () => {
- expect(wrapper.find(Textbox).prop("formattedValue")).toBe(
- navigationKeyOptionObject.text
- );
- });
- });
-
- describe("isOptional", () => {
- it("label has '(optional)' suffix when the isOptional prop is passed to the input", () => {
- const propWrapper = mount(
-
-
-
-
-
-
- );
-
- assertStyleMatch(
- {
- content: '"(optional)"',
- },
- propWrapper.find(StyledLabelContainer),
- { modifier: "::after" }
- );
- });
- });
-
- describe("and the onChange prop is passed", () => {
- it("then that prop should be called with the same value", () => {
- const onChangeFn = jest.fn();
- const wrapper = renderSelect({ ...textboxProps, onChange: onChangeFn });
-
- simulateSelectTextboxEvent(wrapper, "click");
- act(() => {
- wrapper.find(SelectList).prop("onSelect")(clickOptionObject);
- });
- expect(onChangeFn).toHaveBeenCalledWith(expectedEventObject);
- });
- });
-
- describe("by clicking on an Option", () => {
- it("then the SelectList should be closed", () => {
- const wrapper = renderSelect();
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- act(() => {
- wrapper.find(Option).first().simulate("click");
- });
- simulateSelectTextboxEvent(wrapper, "focus");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
- });
-
- describe("when the onSelectListClose is called in the SelectList", () => {
- it("the SelectList should be closed", () => {
- const wrapper = renderSelect();
-
- simulateSelectTextboxEvent(wrapper, "click");
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).toBeVisible();
- act(() => {
- wrapper.find(SelectList).prop("onSelectListClose")();
- });
- expect(
- wrapper.find(StyledSelectListContainer).getDOMNode()
- ).not.toBeVisible();
- });
- });
-
- describe("when the onKeyDown prop is passed", () => {
- const expectedEventObject = {
- key: "ArrowDown",
- };
-
- it("then when a key is pressed, that prop should be called with expected values", () => {
- const onKeyDownFn = jest.fn();
- const wrapper = renderSelect({ onKeyDown: onKeyDownFn });
-
- simulateKeyDown(wrapper, "ArrowDown");
-
- expect(onKeyDownFn).toHaveBeenCalledWith(
- expect.objectContaining({
- ...expectedEventObject,
- })
- );
- });
- });
-
- describe('when the "onBlur" prop has been passed and the input has been blurred', () => {
- it("then that prop should be called", () => {
- const onBlurFn = jest.fn();
- const wrapper = renderSelect({ onBlur: onBlurFn });
-
- simulateSelectTextboxEvent(wrapper, "blur");
- expect(onBlurFn).toHaveBeenCalled();
- });
-
- describe("and there is a mouseDown reported on open list", () => {
- it("then that prop should not be called", () => {
- const onBlurFn = jest.fn();
- const wrapper = renderSelect({ onBlur: onBlurFn, openOnFocus: true });
-
- simulateSelectTextboxEvent(wrapper, "focus");
- wrapper.find(Option).first().simulate("mousedown");
- simulateSelectTextboxEvent(wrapper, "blur");
- expect(onBlurFn).not.toHaveBeenCalled();
- });
- });
-
- it("coverage filler for else path", () => {
- const wrapper = renderSelect();
- simulateSelectTextboxEvent(wrapper, "blur");
- });
- });
-
- describe("when the component is controlled", () => {
- const onChangeFn = jest.fn();
- let wrapper: ReactWrapper;
- const expectedObject = {
- target: {
- id: "testSelect",
- name: "testSelect",
- value: "opt3",
- },
- };
-
- const clickOptionObject = {
- value: "opt3",
- text: "black",
- selectionType: "click",
- selectionConfirmed: true,
- };
-
- beforeEach(() => {
- onChangeFn.mockClear();
- wrapper = renderSelect({ onChange: onChangeFn, value: "opt1" });
- });
-
- describe("and an option is selected", () => {
- it("then the onChange prop should be called with expected value", () => {
- simulateSelectTextboxEvent(wrapper, "click");
- act(() => {
- wrapper.find(SelectList).prop("onSelect")(clickOptionObject);
- });
- expect(onChangeFn).toHaveBeenCalledWith({
- selectionConfirmed: true,
- ...expectedObject,
- });
- });
- });
-
- describe("and a printable character has been typed in the Textbox", () => {
- beforeEach(() => {
- simulateKeyDown(wrapper, "b");
- wrapper.update();
- });
-
- it("then the value should not change", () => {
- expect(wrapper.find(Textbox).prop("value")).toBe("opt1");
- });
-
- it("then the onChange function should have been called with with the expected value", () => {
- expect(onChangeFn).toHaveBeenCalledWith({
- selectionConfirmed: false,
- ...expectedObject,
- });
- });
- });
-
- describe("and an an empty value has been passed", () => {
- it("then the textbox displayed value should be cleared", () => {
- expect(wrapper.find(Textbox).props().formattedValue).toBe("red");
- wrapper.setProps({ value: "" });
- expect(wrapper.update().find(Textbox).props().formattedValue).toBe("");
- });
-
- it("then the textbox value should be cleared", () => {
- expect(wrapper.find(Textbox).props().value).toBe("opt1");
- wrapper.setProps({ value: "" });
- expect(wrapper.update().find(Textbox).props().value).toBe("");
- });
- });
-
- describe("when parent re-renders", () => {
- const WrapperComponent = () => {
- const mockRef = useRef(null);
-
- return (
-
-
-
-
-
-
-
-
- );
- };
-
- it("should persist the input value", () => {
- wrapper = mount();
- simulateSelectTextboxEvent(wrapper, "click");
- act(() => {
- wrapper.find(Option).first().simulate("click");
- });
- expect(wrapper.update().find(Textbox).props().formattedValue).toBe(
- "red"
- );
- wrapper.setProps({ prop: "foo" });
- expect(wrapper.update().find(Textbox).props().formattedValue).toBe(
- "red"
- );
- });
- });
- });
-
- describe("required", () => {
- let wrapper: ReactWrapper;
-
- beforeAll(() => {
- wrapper = renderSelect({ label: "required", required: true });
- });
-
- it("the required prop is passed to the input", () => {
- const input = wrapper.find("input");
- expect(input.getDOMNode()).toHaveAttribute("required", "");
- });
-
- it("should add an asterisk after the label text", () => {
- assertStyleMatch(
- {
- content: '"*"',
- color: "var(--colorsSemanticNegative500)",
- fontWeight: "var(--fontWeights500)",
- marginLeft: "var(--spacing050)",
- },
- wrapper.find(StyledLabel),
- { modifier: "::after" }
- );
- });
- });
-
- it("has the expected border radius styling", () => {
- const wrapper = renderSelect({});
- assertStyleMatch(
- { borderRadius: "var(--borderRadius050)" },
- wrapper.find(StyledInput)
- );
-
- assertStyleMatch(
- { borderRadius: "var(--borderRadius050)" },
- wrapper.find(StyledSelectListContainer)
- );
- });
-
- describe("when the onListScrollBottom prop is set", () => {
- const onListScrollBottomFn = jest.fn();
- it("should not be called when an option is clicked", () => {
- const wrapper = renderSelect(
- {
- onListScrollBottom: onListScrollBottomFn,
- openOnFocus: true,
- },
- mount
- );
-
- act(() => {
- simulateSelectTextboxEvent(wrapper, "focus");
- jest.runOnlyPendingTimers();
- wrapper.update();
- });
- wrapper.find(Option).first().simulate("click");
- expect(onListScrollBottomFn).not.toHaveBeenCalled();
- });
- });
-});
-
-describe("coverage filler for else path", () => {
- const wrapper = renderSelect();
- simulateKeyDown(wrapper, "F1");
-});
-
-describe("when maxWidth is passed", () => {
- it("should be passed to InputPresentation", () => {
- const wrapper = renderSelect({ maxWidth: "67%" });
-
- assertStyleMatch(
- {
- maxWidth: "67%",
- },
- wrapper.find(InputPresentation)
- );
- });
-
- it("renders with maxWidth as 100% when no maxWidth is specified", () => {
- const wrapper = renderSelect({ maxWidth: "" });
-
- assertStyleMatch(
- {
- maxWidth: "100%",
- },
- wrapper.find(InputPresentation)
- );
- });
-});
diff --git a/src/components/select/simple-select/simple-select.test.tsx b/src/components/select/simple-select/simple-select.test.tsx
index f5db342f53..5abd4d7096 100644
--- a/src/components/select/simple-select/simple-select.test.tsx
+++ b/src/components/select/simple-select/simple-select.test.tsx
@@ -1,37 +1,101 @@
import React from "react";
-import { render, screen } from "@testing-library/react";
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { testStyledSystemMarginRTL } from "../../../__spec_helper__/__internal__/test-utils";
+import mockDOMRect from "../../../__spec_helper__/mock-dom-rect";
+import Logger from "../../../__internal__/utils/logger";
import SimpleSelect from ".";
import Option from "../option";
-import Logger from "../../../__internal__/utils/logger";
-test("renders combobox and text overlay that is hidden from assistive technologies", () => {
+beforeEach(() => {
+ // Mock non-zero dimensions for the scrollable container in dropdown list. To ensure react-virtual renders options in the dropdown list correctly.
+ mockDOMRect(40, 100, "select-list-scrollable-container");
+});
+
+afterEach(() => {
+ jest.restoreAllMocks();
+});
+
+test("renders a visually-hidden input box", () => {
render(
- {}} value="">
-
+ {}}>
+
);
- expect(screen.getByRole("combobox")).toBeInTheDocument();
+ const input = screen.getByRole("combobox");
+ expect(input).toBeInTheDocument();
+ expect(input).not.toBeVisible();
+});
- const textOverlay = screen.getByTestId("select-text");
- expect(textOverlay).toBeVisible();
- expect(textOverlay).toHaveAttribute("aria-hidden", "true");
+test("renders input with a textbox role when readOnly prop is true", () => {
+ render(
+ {}} readOnly>
+
+
+ );
+
+ expect(screen.getByRole("textbox")).toBeInTheDocument();
});
-test("initially renders text overlay with placeholder text", () => {
+// Styling test for coverage - styles are covered by Chromatic
+test("applies transparent background and no border to input, when transparent prop is true", () => {
render(
+ {}} transparent>
+
+
+ );
+
+ expect(screen.getByRole("combobox")).toHaveStyle(`
+ background-color: transparent;
+ border: none;
+ `);
+});
+
+test("displays the selected option text, when value prop matches an option", () => {
+ render(
+ {}} value="amber">
+
+
+ );
+
+ expect(screen.getByText("amber", { ignore: "li" })).toBeVisible();
+});
+
+test("clears option selection when value prop is set to an empty string", () => {
+ const { rerender } = render(
+ {}} value="amber">
+
+
+ );
+ rerender(
{}} value="">
-
+
);
- expect(screen.getByTestId("select-text")).toHaveTextContent(
- "Please Select..."
+ expect(screen.getByText("Please Select...")).toBeVisible();
+ expect(screen.getByRole("combobox")).toHaveValue("");
+});
+
+test("displays default placeholder text when no value is selected", () => {
+ render(
+ {}} value="">
+
+
);
+
+ expect(screen.getByText("Please Select...")).toBeVisible();
});
-test("renders text overlay with custom placeholder when placeholder prop is passed", () => {
+test("displays custom text when placeholder prop is provided and no value is selected", () => {
render(
-
+
+
+ );
+
+ expect(screen.getByText("Select a colour")).toBeVisible();
+});
+
+test("hides select text overlay from screen readers using aria-hidden", () => {
+ render(
+ {}}>
+
);
- expect(screen.getByTestId("select-text")).toHaveTextContent(
- "Select a colour"
+ expect(screen.getByTestId("select-text")).toHaveAttribute(
+ "aria-hidden",
+ "true"
);
});
-test("combobox has correct accessible name when label prop is provided", () => {
+describe("accessible name of the input", () => {
+ it("is set to the label prop when provided", () => {
+ render(
+ {}}>
+
+
+ );
+
+ expect(screen.getByRole("combobox")).toHaveAccessibleName("Colour");
+ });
+
+ it("is set to the aria-label prop when provided", () => {
+ render(
+ {}}>
+
+
+ );
+
+ expect(screen.getByRole("combobox")).toHaveAccessibleName("Colour");
+ });
+
+ it("is set to the aria-label when both aria-label and label props are passed", () => {
+ render(
+ {}} aria-label="Colour">
+
+
+ );
+
+ expect(screen.getByRole("combobox")).toHaveAccessibleName("Colour");
+ });
+
+ it("is set to the text referenced by the aria-labelledby prop when provided", () => {
+ render(
+ <>
+ My Select
+ {}}>
+
+
+ >
+ );
+
+ expect(screen.getByRole("combobox")).toHaveAccessibleName("My Select");
+ });
+
+ it("is set to the text referenced by aria-labelledby when both aria-labelledby and aria-label props are passed", () => {
+ render(
+ <>
+ My Select
+ {}}
+ >
+
+
+ >
+ );
+
+ expect(screen.getByRole("combobox")).toHaveAccessibleName("My Select");
+ });
+});
+
+test("associates the dropdown list with the correct accessible name from the label prop", async () => {
+ const user = userEvent.setup();
render(
- {}} value="">
-
+ {}}>
+
);
- expect(screen.getByRole("combobox")).toHaveAccessibleName("Colour");
+ await user.click(screen.getByRole("combobox"));
+
+ expect(await screen.findByRole("listbox")).toHaveAccessibleName("Colour");
});
-test("combobox has correct accessible name when aria-label prop is provided", () => {
+describe("typing into the input", () => {
+ it("selects the first option with text starting with the typed printable character", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "b");
+
+ expect(screen.getByText("blue", { ignore: "li" })).toBeVisible();
+ });
+
+ it("selects the second option with text starting with the typed printable character when typed twice", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "bb");
+
+ expect(screen.getByText("black", { ignore: "li" })).toBeVisible();
+ });
+
+ it("does not change the selected option when no option text starts with the typed printable character", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "bx");
+
+ expect(screen.getByText("blue", { ignore: "li" })).toBeVisible();
+ });
+
+ it("selects the first option with text matching the typed substring when typed quickly", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "bla");
+
+ expect(screen.getByText("black", { ignore: "li" })).toBeVisible();
+ });
+
+ it("selects the first option starting with the latest printable character typed after a long pause", async () => {
+ jest.useFakeTimers();
+
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+ render(
+ {}}>
+
+
+
+
+
+ );
+
+ const input = screen.getByRole("combobox");
+ await user.type(input, "bla");
+ act(() => jest.runOnlyPendingTimers());
+ await user.type(input, "g");
+
+ expect(screen.getByText("green", { ignore: "li" })).toBeVisible();
+
+ jest.useRealTimers();
+ });
+
+ it.each(["Meta", "Control"])(
+ "does not select any option when a printable character is typed while holding down the %s key",
+ async (specialKey) => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+
+
+ );
+
+ // Hold special key down while typing 'b'
+ await user.type(screen.getByRole("combobox"), `{${specialKey}>}b`);
+
+ expect(screen.getByText("Please Select...")).toBeVisible();
+ }
+ );
+
+ it("does not change selected option when value prop is set (controlled usage)", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} value="amber">
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "blue");
+
+ expect(
+ screen.queryByText("blue", { ignore: "li" })
+ ).not.toBeInTheDocument();
+ });
+
+ it("calls onChange prop each time a character typed into the input", async () => {
+ const user = userEvent.setup();
+ const onChange = jest.fn();
+ render(
+
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "bla");
+
+ expect(onChange).toHaveBeenCalledTimes(3);
+ });
+
+ it("calls onChange prop with the value of the matched option after a character is typed into the input", async () => {
+ const user = userEvent.setup();
+ const onChange = jest.fn();
+ render(
+
+
+
+
+
+ );
+
+ await user.type(screen.getByRole("combobox"), "b");
+
+ expect(onChange).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ target: { value: "blue", name: "colour", id: "colour" },
+ selectionConfirmed: false,
+ })
+ );
+ });
+});
+
+describe("dropdown list", () => {
+ it("opens when input is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(await screen.findByRole("listbox")).toBeVisible();
+ });
+
+ it("opens when input's dropdown icon is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+ );
+
+ await user.click(screen.getByTestId("input-icon-toggle"));
+
+ expect(await screen.findByRole("listbox")).toBeVisible();
+ });
+
+ it.each(["Space", "ArrowUp", "ArrowDown", "Home", "End"] as const)(
+ "opens when input is focused and %s key is pressed",
+ async (key) => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+ );
+
+ await user.tab();
+ await user.keyboard(`[${key}]`);
+
+ expect(await screen.findByRole("listbox")).toBeVisible();
+ }
+ );
+
+ it.each(["Enter", "a"] as const)(
+ "does not open when %s key is pressed",
+ async (key) => {
+ const user = userEvent.setup();
+ render(
+ {}}>
+
+
+ );
+
+ await user.tab();
+ await user.keyboard(`[${key}]`);
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ }
+ );
+
+ it("does not open, when input is disabled and is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} disabled>
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+
+ it("does not open, when input is disabled and is selected with the keyboard", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} disabled>
+
+
+ );
+
+ await user.tab();
+ await user.keyboard("[Space]");
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+
+ it("does not open, when input is read-only and is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} readOnly>
+
+
+ );
+
+ await user.click(screen.getByRole("textbox"));
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+
+ it("does not open, when input is read-only and Space bar is pressed", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} readOnly>
+
+
+ );
+
+ await user.tab();
+ await user.keyboard("[Space]");
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+
+ it("opens when input is focused and openOnFocus prop is true", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} openOnFocus>
+
+
+ );
+
+ await user.tab();
+
+ expect(await screen.findByRole("listbox")).toBeVisible();
+ });
+
+ it("does not open, when input is focused and openOnFocus prop is false", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} openOnFocus={false}>
+
+
+ );
+
+ await user.tab();
+
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+
+ it("closes when an option is clicked", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} value="">
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+ await user.click(await screen.findByRole("option", { name: "amber" }));
+
+ await waitFor(() => {
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+ });
+
+ it("closes when an option is focused and Enter key is pressed", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} value="">
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+ await user.keyboard("{ArrowDown}");
+ await user.keyboard("{Enter}");
+
+ await waitFor(() => {
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+ });
+
+ it("closes when input is clicked twice", async () => {
+ const user = userEvent.setup();
+ render(
+ {}} value="">
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+ await user.click(screen.getByRole("combobox"));
+
+ await waitFor(() => {
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+ });
+
+ it("closes when clicking outside the list", async () => {
+ const user = userEvent.setup();
+ render(
+ <>
+ {}} value="">
+
+
+ Outside content
+ >
+ );
+
+ await user.click(screen.getByRole("combobox"));
+ await user.click(screen.getByText("Outside content"));
+
+ await waitFor(() => {
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
+ });
+ });
+});
+
+describe("when onClick prop is passed", () => {
+ it("is called when input is clicked", async () => {
+ const onClick = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}} onClick={onClick}>
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
+
+ it("is not called when disabled input is clicked", async () => {
+ const onClick = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}}
+ onClick={onClick}
+ disabled
+ >
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(onClick).not.toHaveBeenCalled();
+ });
+
+ it("is not called when read-only input is clicked", async () => {
+ const onClick = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}}
+ onClick={onClick}
+ readOnly
+ >
+
+
+ );
+
+ await user.click(screen.getByRole("textbox"));
+
+ expect(onClick).not.toHaveBeenCalled();
+ });
+});
+
+test("calls onOpen when list is opened", async () => {
+ const onOpen = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}} onOpen={onOpen}>
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(onOpen).toHaveBeenCalledTimes(1);
+});
+
+test("calls onKeyDown prop with details of the pressed key, when typing a character into the input", async () => {
+ const user = userEvent.setup();
+ const onKeyDown = jest.fn();
render(
- {}} value="">
-
+ {}} onKeyDown={onKeyDown}>
+
);
- expect(screen.getByRole("combobox")).toHaveAccessibleName("Colour");
+ await user.type(screen.getByRole("combobox"), "{c}");
+
+ expect(onKeyDown).toHaveBeenCalledWith(expect.objectContaining({ key: "c" }));
});
-test("combobox has correct accessible name when aria-labelledby prop is provided", () => {
+test("calls onFocus prop when input is focused", async () => {
+ const onFocus = jest.fn();
+ const user = userEvent.setup();
render(
- <>
- My Select
+ {}} onFocus={onFocus}>
+
+
+ );
+
+ await user.tab();
+
+ expect(onFocus).toHaveBeenCalledTimes(1);
+});
+
+describe("when onBlur prop is passed", () => {
+ it("is called when input loses focus", async () => {
+ const onBlur = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}} onBlur={onBlur}>
+
+
+ );
+
+ await user.tab();
+ await user.tab({ shift: true });
+
+ expect(onBlur).toHaveBeenCalledTimes(1);
+ });
+
+ it("is not called when input loses focus temporarily due to option selection", async () => {
+ const onBlur = jest.fn();
+ const user = userEvent.setup();
+ render(
+ {}} onBlur={onBlur}>
+
+
+ );
+
+ await user.click(screen.getByRole("combobox"));
+
+ await user.click(screen.getByRole("option", { name: "amber" }));
+
+ expect(onBlur).not.toHaveBeenCalled();
+ });
+});
+
+describe("forwarded ref", () => {
+ it("allows access to the input element through a forwarded callback ref", () => {
+ const mockRef = jest.fn((element) => element);
+ render(
+ {}} ref={mockRef}>
+
+
+ );
+
+ expect(mockRef).toHaveBeenNthCalledWith(1, screen.getByRole("combobox"));
+ });
+
+ it("allows access to the input element through a forwarded ref object", () => {
+ const mockRef = { current: null };
+ render(
+ {}} ref={mockRef}>
+
+
+ );
+
+ expect(mockRef.current).toBe(screen.getByRole("combobox"));
+ });
+});
+
+describe("deprecation warnings", () => {
+ it("raises deprecation warning when component is used with defaultValue and no onChange (uncontrolled usage)", () => {
+ jest.spyOn(console, "warn").mockImplementation(() => {});
+
+ const loggerSpy = jest.spyOn(Logger, "deprecate");
+ render(
+
+
+
+ );
+
+ expect(loggerSpy).toHaveBeenNthCalledWith(
+ 1,
+ "Uncontrolled behaviour in `Simple Select` is deprecated and support will soon be removed. Please make sure all your inputs are controlled."
+ );
+ });
+
+ it("should not display deprecation about uncontrolled Textbox when parent component is controlled", () => {
+ const loggerSpy = jest.spyOn(Logger, "deprecate");
+ render(
{}}
- value=""
+ value="1"
+ placeholder="Select a colour"
>
- >
+ );
+
+ expect(loggerSpy).not.toHaveBeenCalled();
+ loggerSpy.mockClear();
+ });
+
+ it("should not display deprecation about uncontrolled Textbox when parent component is not controlled", () => {
+ const loggerSpy = jest.spyOn(Logger, "deprecate");
+ render(
+
+
+
+ );
+
+ expect(loggerSpy).not.toHaveBeenCalledWith(
+ "Uncontrolled behaviour in `Textbox` is deprecated and support will soon be removed. Please make sure all your inputs are controlled."
+ );
+ loggerSpy.mockClear();
+ });
+});
+
+it("marks input as required when required prop is true", () => {
+ render(
+ {}} required>
+
+
);
- expect(screen.getByRole("combobox")).toHaveAccessibleName("My Select");
+ expect(screen.getByRole("combobox")).toBeRequired();
});
-test("should not display deprecation about uncontrolled Textbox when parent component is controlled", () => {
- const loggerSpy = jest.spyOn(Logger, "deprecate");
+test("does not call onOpen, when openOnFocus is true and the input is refocused while the dropdown list is already open", () => {
+ jest.useFakeTimers();
+
+ const onOpen = jest.fn();
render(
{}}
- value="1"
- placeholder="Select a colour"
+ onOpen={onOpen}
+ openOnFocus
>
-
+
);
- expect(loggerSpy).not.toHaveBeenCalled();
- loggerSpy.mockClear();
-});
+ const input = screen.getByRole("combobox");
-test("should not display deprecation about uncontrolled Textbox when parent component is not controlled", () => {
- const loggerSpy = jest.spyOn(Logger, "deprecate");
- render(
-
-
-
- );
+ // open dropdown list
+ fireEvent.focus(input);
+ act(() => jest.runOnlyPendingTimers());
- expect(loggerSpy).not.toHaveBeenCalledWith(
- "Uncontrolled behaviour in `Textbox` is deprecated and support will soon be removed. Please make sure all your inputs are controlled."
- );
- loggerSpy.mockClear();
+ onOpen.mockClear();
+
+ // refocus input
+ fireEvent.focus(input);
+ act(() => jest.runOnlyPendingTimers());
+
+ expect(onOpen).not.toHaveBeenCalled();
+
+ jest.useRealTimers();
});
+
+testStyledSystemMarginRTL(
+ (props) => (
+ {}}
+ >
+
+
+ ),
+ () => screen.getByTestId("my-select")
+);