Skip to content

Commit

Permalink
feat: dropdown aria 속성 추가, test 코드 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
SeieunYoo committed Sep 21, 2024
1 parent 61f91a1 commit 081b086
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 1 deletion.
207 changes: 207 additions & 0 deletions packages/wow-ui/src/components/DropDown/DropDown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"use client";

import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import type { ReactNode } from "react";
import { useState } from "react";

import DropDown from "@/components/DropDown";
import DropDownOption from "@/components/DropDown/DropDownOption";

const dropdownId = "dropdown-id";

const options = [
{ value: "option1", text: "Option 1" },
{ value: "option2", text: "Option 2" },
{ value: "option3", text: "Option 3" },
];

const renderDropDown = (props = {}) =>
render(
<DropDown {...props} id={dropdownId}>
{options.map((option) => (
<DropDownOption
key={option.value}
text={option.text}
value={option.value}
/>
))}
</DropDown>
);

describe("DropDown component", () => {
it("should render the placeholder", () => {
renderDropDown({ placeholder: "Please select" });
expect(screen.getByText("Please select")).toBeInTheDocument();
});

it("should allow selection of an option", async () => {
const dropdownId = "dropdown-id";
renderDropDown({ id: dropdownId, placeholder: "Please select" });

await userEvent.click(screen.getByText("Please select"));
await userEvent.click(screen.getByText("Option 1"));

await waitFor(() => {
const option1 = document.querySelector(`#${dropdownId}-option-option1`);
expect(option1).toHaveAttribute("aria-selected", "true");
});
});

it("should render with default value", () => {
renderDropDown({
defaultValue: "option2",
placeholder: "Please select",
});

const dropdownButton = screen.getByRole("button", {
name: "Option 2 down-arrow icon",
});
expect(dropdownButton).toBeInTheDocument();
expect(dropdownButton).toHaveAttribute("id", `${dropdownId}-trigger`);
expect(dropdownButton).toHaveTextContent("Option 2");
});

it("should render the trigger button", async () => {
renderDropDown({ trigger: <button>Open Dropdown</button> });

userEvent.click(screen.getByText("Open Dropdown"));

await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
});

it("closes dropdown when clicking outside", async () => {
renderDropDown({ placeholder: "Select an option" });
await userEvent.click(screen.getByText("Select an option"));
await userEvent.click(document.body);

await waitFor(() => {
const dropdown = screen.queryByRole("listbox");
expect(dropdown).toBeNull();
});
});
});

describe("external control and events", () => {
it("should fire external onChange event and update controlled state", async () => {
const ControlledDropDown = () => {
const [selectedValue, setSelectedValue] = useState("");

const handleChange = (value: {
selectedValue: string;
selectedText: ReactNode;
}) => {
setSelectedValue(value.selectedValue);
};

return (
<DropDown
id={dropdownId}
placeholder="Please select"
value={selectedValue}
onChange={handleChange}
>
{options.map((option) => (
<DropDownOption
key={option.value}
text={option.text}
value={option.value}
/>
))}
</DropDown>
);
};

render(<ControlledDropDown />);

await userEvent.click(screen.getByText("Please select"));
await userEvent.click(screen.getByText("Option 2"));

await waitFor(() => {
const option2 = document.querySelector(`#${dropdownId}-option-option2`);
expect(option2).toHaveAttribute("aria-selected", "true");
});
});

it("should navigate options using keyboard and apply focus correctly", async () => {
const ControlledDropDown = () => {
const [selectedValue, setSelectedValue] = useState("");

const handleChange = (value: {
selectedValue: string;
selectedText: ReactNode;
}) => {
setSelectedValue(value.selectedValue);
};

return (
<DropDown
id={dropdownId}
placeholder="Please select"
value={selectedValue}
onChange={handleChange}
>
{options.map((option) => (
<DropDownOption
key={option.value}
text={option.text}
value={option.value}
/>
))}
</DropDown>
);
};

render(<ControlledDropDown />);
await userEvent.click(screen.getByText("Please select"));

await userEvent.keyboard("{ArrowDown}");
await waitFor(() => {
const dropdown = screen.getByRole("listbox");
expect(dropdown).toHaveAttribute(
"aria-activedescendant",
`${dropdownId}-option-option1`
);
});

await userEvent.keyboard("{ArrowDown}");
await waitFor(() => {
const dropdown = screen.getByRole("listbox");
expect(dropdown).toHaveAttribute(
"aria-activedescendant",
`${dropdownId}-option-option2`
);
});

await userEvent.keyboard("{ArrowDown}");
await waitFor(() => {
const dropdown = screen.getByRole("listbox");
expect(dropdown).toHaveAttribute(
"aria-activedescendant",
`${dropdownId}-option-option3`
);
});

await userEvent.keyboard("{ArrowUp}");
await waitFor(() => {
const dropdown = screen.getByRole("listbox");
expect(dropdown).toHaveAttribute(
"aria-activedescendant",
`${dropdownId}-option-option2`
);
});

await userEvent.keyboard("{Enter}");
await waitFor(() => {
const option2 = document.querySelector(`#${dropdownId}-option-option2`);
expect(option2).toHaveAttribute("aria-selected", "true");

const option1 = document.querySelector(`#${dropdownId}-option-option1`);
const option3 = document.querySelector(`#${dropdownId}-option-option3`);
expect(option1).toHaveAttribute("aria-selected", "false");
expect(option3).toHaveAttribute("aria-selected", "false");
});
});
});
1 change: 1 addition & 0 deletions packages/wow-ui/src/components/DropDown/DropDownOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const DropDownOption = forwardRef<HTMLLIElement, DropDownOptionProps>(

return (
<styled.li
aria-selected={isSelected}
id={`${dropdownId}-option-${value}`}
ref={ref}
role="option"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const DropDownOptionList = ({
children,
hasCustomTrigger,
}: DropDownWrapperProps) => {
const { open, setFocusedValue, focusedValue, handleSelect } =
const { open, setFocusedValue, focusedValue, handleSelect, dropdownId } =
useDropDownContext();
const itemMap = useCollection();
const listRef = useRef<HTMLUListElement>(null);
Expand Down Expand Up @@ -64,6 +64,10 @@ export const DropDownOptionList = ({

return (
<styled.ul
{...(focusedValue && {
"aria-activedescendant": `${dropdownId}-option-${focusedValue}`,
})}
aria-hidden={!open}
display="flex"
flexDirection="column"
ref={listRef}
Expand Down

0 comments on commit 081b086

Please sign in to comment.