Skip to content

Commit

Permalink
add test
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenjwatkins committed Jan 14, 2025
1 parent 656cc97 commit da5217e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 21 deletions.
106 changes: 103 additions & 3 deletions easy-ui-react/src/MultiSelect/MultiSelect.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getByRole, screen } from "@testing-library/react";
import { UserEvent } from "@testing-library/user-event";
import React, { useCallback, useState } from "react";
import { vi } from "vitest";
import { mockGetComputedStyle } from "../utilities/test";
import { mockGetComputedStyle, render, userClick } from "../utilities/test";
import { Item, MultiSelect, useFilter, useListData } from "./MultiSelect";

describe("<MultiSelect />", () => {
let restoreGetComputedStyle: () => void;
Expand All @@ -14,7 +18,103 @@ describe("<MultiSelect />", () => {
restoreGetComputedStyle();
});

it("should show on trigger click", async () => {
expect(true).toBe(true);
it("should render a ComboBox", async () => {
render(getMultiSelect());
expect(screen.getByRole("combobox")).toBeInTheDocument();
});

it("should open dropdown with arrow button", async () => {
const { user } = render(getMultiSelect());
await userClick(user, screen.getByText("Open dropdown"));
expect(screen.getByRole("listbox")).toBeInTheDocument();
});

it("should filter dropdown with input interaction", async () => {
const { user } = render(getMultiSelect());
await user.type(screen.getByRole("combobox"), "ban");
expect(screen.getByRole("option", { name: "Banana" })).toBeInTheDocument();
});

it("should select item", async () => {
const { user } = render(getMultiSelect());
await userClick(user, screen.getByText("Open dropdown"));
const options = screen.getAllByRole("option");
await userClick(user, options[1]);
expect(getSelectedItem("Banana")).toBeInTheDocument();
});

it("should remove selected item", async () => {
const { user } = render(
getMultiSelect({
initialSelectedItems: [fruits[0]],
}),
);
await userClick(user, screen.getByText("Open dropdown"));
expect(getSelectedItem("Apple")).toBeInTheDocument();
await clearSelectedItem(user, "Apple");
expect(screen.queryByText("No selected items")).toBeInTheDocument();
});
});

const fruits = [
{ key: 1, label: "Apple" },
{ key: 2, label: "Banana" },
{ key: 3, label: "Cherry" },
] as const satisfies Item[];

const getMultiSelect = ({
initialSelectedItems = [],
}: {
initialSelectedItems?: Item[];
} = {}) => {
function MultiSelectTest() {
const [selectedItems, setSelectedItems] =
useState<Item[]>(initialSelectedItems);
const { contains } = useFilter({ sensitivity: "base" });
const filter = useCallback(
(item: Item, filterText: string) => contains(item.label, filterText),
[contains],
);
const list = useListData<Item>({
initialSelectedKeys: selectedItems.map((i) => i.key),
initialItems: fruits,
filter,
});
return (
<MultiSelect
dropdownItems={list.items}
inputValue={list.filterText}
onInputChange={list.setFilterText}
selectedItems={selectedItems}
onSelectionChange={setSelectedItems}
placeholder="Select a fruit"
maxItemsUntilScroll={10}
renderPill={(item) => (
<MultiSelect.Pill icon={item.icon} label={item.label} />
)}
>
{(item) => (
<MultiSelect.Option textValue={item.label}>
<MultiSelect.OptionText>{item.label}</MultiSelect.OptionText>
</MultiSelect.Option>
)}
</MultiSelect>
);
}
return <MultiSelectTest />;
};

function getSelectedItem(name: string) {
const selectedItems = screen.getByLabelText("Selected items");
return getByRole(selectedItems, "row", { name, hidden: true });
}

async function clearSelectedItem(user: UserEvent, name: string) {
await userClick(
user,
getByRole(getSelectedItem(name), "button", {
name: /remove/i,
hidden: true,
}),
);
}
8 changes: 7 additions & 1 deletion easy-ui-react/src/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { useElementWidth } from "./utilities";

import styles from "./MultiSelect.module.scss";
import { Text } from "../Text";

export type Item = { key: Key } & PillProps;

Expand Down Expand Up @@ -216,7 +217,7 @@ export function MultiSelect<T extends Item>(props: MultiSelectProps<T>) {

return (
<div ref={rootRef} className={styles.MultiSelect}>
{selectedItems.length > 0 && (
{selectedItems.length > 0 ? (
<PillGroup
items={selectedItems}
horizontalStackContainerProps={{ gap: "1" }}
Expand All @@ -225,6 +226,10 @@ export function MultiSelect<T extends Item>(props: MultiSelectProps<T>) {
>
{renderPill}
</PillGroup>
) : (
<VisuallyHidden>
<Text>No selected items</Text>
</VisuallyHidden>
)}
<div className={styles.comboBoxContainer}>
<ComboBox
Expand Down Expand Up @@ -270,6 +275,7 @@ export function MultiSelect<T extends Item>(props: MultiSelectProps<T>) {
onClick={() => clearComboBoxButtonRef.current?.click()}
tabIndex={-1}
>
<Text visuallyHidden>Open dropdown</Text>
<Icon symbol={KeyboardArrowDownIcon} />
</button>
</div>
Expand Down
27 changes: 10 additions & 17 deletions easy-ui-react/src/MultiSelect/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { RefObject, useEffect, useState } from "react";
import { useResizeObserver } from "@react-aria/utils";
import { RefObject, useState } from "react";

export function useElementWidth(elementRef: RefObject<HTMLDivElement>) {
const [width, setWidth] = useState(0);

useEffect(() => {
const trigger = elementRef.current;
if (!trigger) {
return;
}

const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
setWidth(entry.target.clientWidth);
useResizeObserver({
ref: elementRef,
onResize() {
if (elementRef.current) {
const { width } = elementRef.current.getBoundingClientRect();
setWidth(width);
}
});

observer.observe(trigger);
return () => {
observer.unobserve(trigger);
};
}, [elementRef]);
},
});

return width;
}

0 comments on commit da5217e

Please sign in to comment.