Skip to content
Snippets Groups Projects
Unverified Commit 344faf7f authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Add Select all in ListValuePicker (#47865)

* Unify FilterPickerForm

* Unify FilterPickerForm

* Add Select all in ListValuePicker

* Layout changes

* Layout changes

* Add tests

* Add tests

* Revert "Unify FilterPickerForm"

This reverts commit a7dfe884.

* Revert "Unify FilterPickerForm"

This reverts commit 8f002b87.
parent 381c321a
No related branches found
No related tags found
No related merge requests found
......@@ -369,12 +369,13 @@ describe("StringFilterValuePicker", () => {
});
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes[0]).toHaveAccessibleName("In-progress");
expect(checkboxes[0]).toBeChecked();
expect(checkboxes[1]).toHaveAccessibleName("To-do");
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("Completed");
expect(checkboxes[0]).toHaveAccessibleName("Select all");
expect(checkboxes[1]).toHaveAccessibleName("In-progress");
expect(checkboxes[1]).toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("To-do");
expect(checkboxes[2]).not.toBeChecked();
expect(checkboxes[3]).toHaveAccessibleName("Completed");
expect(checkboxes[3]).not.toBeChecked();
});
it("should not elevate selected field values after checking an item", async () => {
......@@ -404,12 +405,13 @@ describe("StringFilterValuePicker", () => {
/>,
);
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes[0]).toHaveAccessibleName("In-progress");
expect(checkboxes[0]).toBeChecked();
expect(checkboxes[1]).toHaveAccessibleName("To-do");
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("Completed");
expect(checkboxes[2]).toBeChecked();
expect(checkboxes[0]).toHaveAccessibleName("Select all");
expect(checkboxes[1]).toHaveAccessibleName("In-progress");
expect(checkboxes[1]).toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("To-do");
expect(checkboxes[2]).not.toBeChecked();
expect(checkboxes[3]).toHaveAccessibleName("Completed");
expect(checkboxes[3]).toBeChecked();
});
it("should not elevate selected field values after unchecking an item", async () => {
......@@ -439,12 +441,13 @@ describe("StringFilterValuePicker", () => {
/>,
);
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes[0]).toHaveAccessibleName("In-progress");
expect(checkboxes[0]).not.toBeChecked();
expect(checkboxes[1]).toHaveAccessibleName("Completed");
expect(checkboxes[1]).toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("To-do");
expect(checkboxes[2]).not.toBeChecked();
expect(checkboxes[0]).toHaveAccessibleName("Select all");
expect(checkboxes[1]).toHaveAccessibleName("In-progress");
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("Completed");
expect(checkboxes[2]).toBeChecked();
expect(checkboxes[3]).toHaveAccessibleName("To-do");
expect(checkboxes[3]).not.toBeChecked();
});
it("should handle empty field values", async () => {
......@@ -1024,12 +1027,13 @@ describe("NumberFilterValuePicker", () => {
});
const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes[0]).toHaveAccessibleName("In-progress");
expect(checkboxes[0]).toBeChecked();
expect(checkboxes[1]).toHaveAccessibleName("To-do");
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("Completed");
expect(checkboxes[0]).toHaveAccessibleName("Select all");
expect(checkboxes[1]).toHaveAccessibleName("In-progress");
expect(checkboxes[1]).toBeChecked();
expect(checkboxes[2]).toHaveAccessibleName("To-do");
expect(checkboxes[2]).not.toBeChecked();
expect(checkboxes[3]).toHaveAccessibleName("Completed");
expect(checkboxes[3]).not.toBeChecked();
});
});
......
......@@ -57,11 +57,21 @@ function CheckboxListPicker({
elevatedValues,
);
const visibleOptions = searchOptions(options, searchValue);
const isAll = options.length === selectedValues.length;
const isNone = selectedValues.length === 0;
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.currentTarget.value);
};
const handleToggleAll = () => {
if (isAll) {
onChange([]);
} else {
onChange(options.map(option => option.value));
}
};
return (
<Stack>
<TextInput
......@@ -70,24 +80,34 @@ function CheckboxListPicker({
autoFocus={autoFocus}
onChange={handleInputChange}
/>
<Checkbox.Group value={selectedValues} onChange={onChange}>
{visibleOptions.length > 0 ? (
<Stack>
{visibleOptions.map(option => (
<Checkbox
key={option.value}
value={option.value}
label={option.label}
/>
))}
</Stack>
) : (
<Stack c="text-light" justify="center" align="center">
<Icon name="search" size={40} />
<Text c="text-medium" fw="bold">{t`Didn't find anything`}</Text>
</Stack>
)}
</Checkbox.Group>
{visibleOptions.length > 0 ? (
<Stack>
<Checkbox
variant="stacked"
label={isAll ? `Select none` : t`Select all`}
checked={isAll}
indeterminate={!isAll && !isNone}
fw="bold"
onChange={handleToggleAll}
/>
<Checkbox.Group value={selectedValues} onChange={onChange}>
<Stack>
{visibleOptions.map(option => (
<Checkbox
key={option.value}
value={option.value}
label={option.label}
/>
))}
</Stack>
</Checkbox.Group>
</Stack>
) : (
<Stack c="text-light" justify="center" align="center">
<Icon name="search" size={40} />
<Text c="text-medium" fw="bold">{t`Didn't find anything`}</Text>
</Stack>
)}
</Stack>
);
}
......
import userEvent from "@testing-library/user-event";
import { renderWithProviders, screen } from "__support__/ui";
import type { FieldValue } from "metabase-types/api";
import { PRODUCT_CATEGORY_VALUES } from "metabase-types/api/mocks/presets";
import { ListValuePicker } from "./ListValuePicker";
type SetupOpts = {
fieldValues?: FieldValue[];
selectedValues?: string[];
placeholder?: string;
shouldCreate?: (query: string, values: string[]) => boolean;
autoFocus?: boolean;
compact?: boolean;
};
function setup({
fieldValues = [],
selectedValues = [],
placeholder = "Search the list",
shouldCreate,
autoFocus,
compact,
}: SetupOpts) {
const onChange = jest.fn();
const onFocus = jest.fn();
const onBlur = jest.fn();
renderWithProviders(
<ListValuePicker
fieldValues={fieldValues}
selectedValues={selectedValues}
placeholder={placeholder}
shouldCreate={shouldCreate}
autoFocus={autoFocus}
compact={compact}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
/>,
);
return { onChange, onFocus, onBlur };
}
describe("ListValuePicker", () => {
describe("checkbox list mode", () => {
const allOptions = PRODUCT_CATEGORY_VALUES.values;
const allValues = allOptions.map(([value]) => String(value));
it("should allow to select all options", async () => {
const { onChange } = setup({
fieldValues: allOptions,
selectedValues: [],
});
const checkbox = screen.getByLabelText("Select all");
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(onChange).toHaveBeenCalledWith(allValues);
});
it("should allow to select all options when some are selected", async () => {
const { onChange } = setup({
fieldValues: allOptions,
selectedValues: [allValues[0]],
});
const checkbox = screen.getByLabelText("Select all");
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(onChange).toHaveBeenCalledWith(allValues);
});
it("should allow to select all options after search", async () => {
const { onChange } = setup({
fieldValues: allOptions,
selectedValues: [],
});
await userEvent.type(
screen.getByPlaceholderText("Search the list"),
allValues[0],
);
expect(screen.getByLabelText(allValues[0])).toBeInTheDocument();
expect(screen.queryByLabelText(allValues[1])).not.toBeInTheDocument();
const checkbox = screen.getByLabelText("Select all");
expect(checkbox).not.toBeChecked();
await userEvent.click(checkbox);
expect(onChange).toHaveBeenCalledWith(allValues);
});
it("should allow to deselect all options", async () => {
const { onChange } = setup({
fieldValues: allOptions,
selectedValues: allValues,
});
const checkbox = screen.getByLabelText("Select none");
expect(checkbox).toBeChecked();
await userEvent.click(checkbox);
expect(onChange).toHaveBeenCalledWith([]);
});
it("should allow to deselect all options after search", async () => {
const { onChange } = setup({
fieldValues: allOptions,
selectedValues: allValues,
});
await userEvent.type(
screen.getByPlaceholderText("Search the list"),
allValues[0],
);
expect(screen.getByLabelText(allValues[0])).toBeInTheDocument();
expect(screen.queryByLabelText(allValues[1])).not.toBeInTheDocument();
const checkbox = screen.getByLabelText("Select none");
expect(checkbox).toBeChecked();
await userEvent.click(checkbox);
expect(onChange).toHaveBeenCalledWith([]);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment