Skip to content

Commit

Permalink
add a select all option
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Oct 24, 2020
1 parent ed37a07 commit a706efb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 24 deletions.
55 changes: 36 additions & 19 deletions public-dashboard-client/src/controls/CohortSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
DropdownWrapper as DropdownWrapperBase,
} from "./shared";

const SELECT_ALL_ID = "ALL";

const DropdownWrapper = styled(DropdownWrapperBase)`
${ControlValue} {
border: 0;
Expand Down Expand Up @@ -77,13 +79,22 @@ const MenuItemContents = styled.div`
width: 100%;
`;

export default function CohortSelectMenu({ onChange, onHighlight, options }) {
const [selected, setSelected] = useState(options);
export default function CohortSelectMenu({
onChange,
onHighlight,
options: optionsFromData,
}) {
const [selected, setSelected] = useState(optionsFromData);

useEffect(() => {
onChange(selected);
}, [onChange, selected]);

const visibleOptions = [
{ id: SELECT_ALL_ID, label: "Select all" },
...optionsFromData,
];

const {
isOpen,
getToggleButtonProps,
Expand All @@ -92,7 +103,7 @@ export default function CohortSelectMenu({ onChange, onHighlight, options }) {
getItemProps,
highlightedIndex,
} = useSelect({
items: options,
items: visibleOptions,
selectedItem: null,
stateReducer: (state, actionAndChanges) => {
const { changes, type } = actionAndChanges;
Expand All @@ -115,28 +126,37 @@ export default function CohortSelectMenu({ onChange, onHighlight, options }) {
if (!selectedItem) {
return;
}
const newSelection = [...selected];

const index = selected.indexOf(selectedItem);
let newSelection;

if (index === -1) {
newSelection.push(selectedItem);
if (selectedItem.id === SELECT_ALL_ID) {
newSelection = [...optionsFromData];
} else {
newSelection.splice(index, 1);
newSelection = [...selected];

const index = selected.indexOf(selectedItem);

if (index === -1) {
newSelection.push(selectedItem);
} else {
newSelection.splice(index, 1);
}
// need to keep selection sorted or labels and colors will get out of sync
newSelection.sort((a, b) => ascending(a.label, b.label));
}
// need to keep selection sorted or labels and colors will get out of sync
newSelection.sort((a, b) => ascending(a.label, b.label));

setSelected(newSelection);
},
});

useEffect(() => {
if (highlightedIndex === -1) {
// index 0 is select all and should be ignored here
if (highlightedIndex < 1) {
onHighlight(undefined);
} else {
onHighlight(options[highlightedIndex]);
// offset by one due to select all
onHighlight(optionsFromData[highlightedIndex - 1]);
}
}, [highlightedIndex, onHighlight, options]);
}, [highlightedIndex, onHighlight, optionsFromData]);

const firstSelected = selected[0];
const buttonContents = (
Expand All @@ -161,7 +181,7 @@ export default function CohortSelectMenu({ onChange, onHighlight, options }) {
</ControlValue>
<DropdownMenu {...getMenuProps()} as="ul">
{isOpen &&
options.map((opt, index) => {
visibleOptions.map((opt, index) => {
const isSelected = selected.includes(opt);
const itemProps = getItemProps({ item: opt, index });
return (
Expand Down Expand Up @@ -191,9 +211,6 @@ CohortSelectMenu.propTypes = {
onChange: PropTypes.func.isRequired,
onHighlight: PropTypes.func.isRequired,
options: PropTypes.arrayOf(
and([
DropdownOptionType,
PropTypes.shape({ color: PropTypes.string.isRequired }),
])
and([DropdownOptionType, PropTypes.shape({ color: PropTypes.string })])
).isRequired,
};
33 changes: 28 additions & 5 deletions public-dashboard-client/src/controls/CohortSelect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ test("triggers menu from button", () => {
expect(menu).toBeInTheDocument();
const { getByRole: getByRoleWithinMenu } = within(menu);
testOptions.forEach((opt) => {
expect(
getByRoleWithinMenu("option", { name: opt.label })
).toBeInTheDocument();
expect(getByRoleWithinMenu("option", { name: opt.label })).toBeVisible();
});
});

Expand All @@ -85,7 +83,7 @@ test("selects all by default", () => {
testOptions.forEach((opt) => {
expect(
getByRole("option", { name: opt.label, selected: true })
).toBeInTheDocument();
).toBeVisible();
});
});

Expand Down Expand Up @@ -139,7 +137,7 @@ test("applies colors to selected items", () => {
);
});

test("passes highlighted option to callback", async () => {
test("passes highlighted option to callback", () => {
const { getByRole } = openMenu();
testOptions.forEach((opt) => {
mockOnHighlight.mockClear();
Expand All @@ -148,3 +146,28 @@ test("passes highlighted option to callback", async () => {
expect(mockOnHighlight.mock.calls[0][0]).toBe(opt);
});
});

test("supports select-all", () => {
const { getByRole } = openMenu();
const selectAll = getByRole("option", { name: /select all/i });
act(() => userEvent.click(selectAll));
// should have no effect because everything is already selected; does NOT de-select all
testOptions.forEach((opt) => {
expect(
getByRole("option", { name: opt.label, selected: true })
).toBeVisible();
});
// now de-select some manually and try again
testOptions.slice(2, 6).forEach((opt) => {
act(() =>
userEvent.click(getByRole("option", { name: opt.label, selected: true }))
);
});
act(() => userEvent.click(selectAll));
// everything is selected again
testOptions.forEach((opt) => {
expect(
getByRole("option", { name: opt.label, selected: true })
).toBeVisible();
});
});

0 comments on commit a706efb

Please sign in to comment.