Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Test files: `ComponentName.test.tsx`
## Coding Conventions

- **Indentation**: tabs
- **Comments**: do not add code comments unless strictly necessary β€” only to state a non-obvious constraint the code itself can't show. Never add comments that narrate what the next line does or why a change is correct.
- **Quotes**: single quotes
- **Line width**: 120 characters
- **TypeScript**: strict mode (`strict`, `noUnusedLocals`, `noUnusedParameters`)
Expand Down
62 changes: 59 additions & 3 deletions lib/components/Filter/Filter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { queryByAttribute, render, screen } from '@testing-library/react';
import { act, queryByAttribute, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';

import { Filter } from './Filter';
import { Option } from './Filter.types';
import { BadgeMultiSelectProps, DateFilterDropdownProps } from './components';
import { resetEvent } from './events';

const options: Option[] = [
{
Expand Down Expand Up @@ -95,7 +96,11 @@ describe('FilterComponent', () => {
};

const getOptionBadge = async (option: string) => {
return queryByAttribute('data-label', component, new RegExp(option, 'i'));
return queryByAttribute(
'data-label',
document.body,
new RegExp(option, 'i'),
);
};

const getApplyButton = async () => {
Expand All @@ -111,7 +116,11 @@ describe('FilterComponent', () => {
};

const getDateButtonByLabel = async (date: string) => {
return queryByAttribute('aria-label', component, new RegExp(date, 'i'));
return queryByAttribute(
'aria-label',
document.body,
new RegExp(date, 'i'),
);
};

return {
Expand Down Expand Up @@ -256,6 +265,53 @@ describe('FilterComponent', () => {
expect(mockOnApply).toHaveBeenCalledWith(expect.any(Date));
});

it('should close the dropdown when clicking outside', async () => {
const { user, getBadgeButton, getOptionBadge } = setup();

await user.click(await getBadgeButton('Badge'));

expect(await getOptionBadge(options.at(0)!.id)).toBeInTheDocument();

await user.click(document.body);

expect(await getOptionBadge(options.at(0)!.id)).not.toBeInTheDocument();
});

it('should keep the dropdown open when clicking inside the portaled menu', async () => {
const { user, getBadgeButton, getOptionBadge, getApplyButton } = setup();

await user.click(await getBadgeButton('Badge'));

const option = await getOptionBadge(options.at(0)!.id);

await user.click(option!);

expect(await getApplyButton()).toBeInTheDocument();
expect(await getOptionBadge(options.at(0)!.id)).toBeInTheDocument();
});

it('should clear applied values when the reset event fires', async () => {
const mockOnApply = vi.fn();

const { user, getBadgeButton, getOptionBadge, getApplyButton } = setup({
onApplyBadge: mockOnApply,
});

await user.click(await getBadgeButton('Badge'));
await user.click((await getOptionBadge(options.at(0)!.id))!);
await user.click(await getApplyButton());

expect(mockOnApply).toHaveBeenCalledWith([
{ id: 'creating', label: 'Creating', variant: 'warning' },
]);

act(() => {
resetEvent();
});

expect(mockOnApply).toHaveBeenCalledWith([]);
});

it('should select a date and reset the values', async () => {
const mockOnApply = vi.fn();

Expand Down
2 changes: 1 addition & 1 deletion lib/components/Filter/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const Filter: FilterComponentProps = ({
children,
closeOnApply = true,
}) => (
<FilterProvider closeOnApply={closeOnApply}>
<FilterProvider closeOnApply={closeOnApply} theme={theme}>
<div className={cn(filterVariants({ className }))} data-theme={theme}>
{children}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
} from 'react';
import { useCallback, useEffect, useId, useMemo, useState } from 'react';

import { useFilterContext } from '@/components/Filter/contexts';
import { FilterEvent, sendOpenFilterEvent } from '@/components/Filter/events';
Expand All @@ -22,7 +15,6 @@ export const useBadgeMultiSelect = ({
}: Pick<BadgeMultiSelectProps, 'onApply' | 'options'>) => {
const { closeOnApply } = useFilterContext();
const id = useId();
const wrapperRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions[]>([]);

Expand Down Expand Up @@ -59,19 +51,18 @@ export const useBadgeMultiSelect = ({
};
}, [id, onApply]);

const handleOpen = useCallback(
() =>
setIsOpen((prev) => {
if (!prev) {
sendOpenFilterEvent(id);
setSelectedOptions((prevOptions) =>
prevOptions.filter((option) => option.isApplied),
);
}
const handleOpenChange = useCallback(
(open: boolean) => {
if (open) {
sendOpenFilterEvent(id);
setSelectedOptions((prevOptions) =>
prevOptions.filter((option) => option.isApplied),
);
}

return !prev;
}),
[id, setIsOpen],
setIsOpen(open);
},
[id],
);

const handleClose = useCallback(() => setIsOpen(false), []);
Expand Down Expand Up @@ -152,16 +143,6 @@ export const useBadgeMultiSelect = ({
useEffect(() => {
const controller = new AbortController();

const handleClickOutside = (event: MouseEvent) => {
if (!wrapperRef.current?.contains(event.target as Node)) {
handleClose();
}
};

document.addEventListener('mousedown', handleClickOutside, {
signal: controller.signal,
});

document.addEventListener(
'visibilitychange',
() => {
Expand All @@ -177,16 +158,15 @@ export const useBadgeMultiSelect = ({
return () => {
controller.abort();
};
}, [handleClose, wrapperRef]);
}, [handleClose]);

return {
isOpen,
isAllSelected,
selectedCount,
selectedOptions,
wrapperRef,
handleApplyOptions,
handleOpen,
handleOpenChange,
handleResetOptions,
handleSelectAll,
handleSelectOption,
Expand Down
Loading
Loading