>({
topping: 'pearls',
})
return (
filters={filtersDependent}
values={values}
onValuesChange={setValues}
/>
)
}
const { queryByRole, getByRole } = render()
await waitForI18nContent()
expect(queryByRole('button', { name: 'Topping : Pearls' })).not.toBeInTheDocument()
await user.click(getByRole('button', { name: 'Check values' }))
await waitFor(() => {
expect(checkValues).toHaveBeenCalledWith({})
})
})
it('clears the value and removes a filter which loses usability', async () => {
const checkValues = vi.fn()
const Wrapper = (): JSX.Element => {
const [values, setValues] = useState>({
flavour: 'jasmine-milk-tea',
topping: 'pearls',
})
return (
filters={filtersDependent}
values={values}
onValuesChange={setValues}
/>
)
}
const { getByRole } = render()
await waitForI18nContent()
expect(getByRole('button', { name: 'Flavour : Jasmine Milk Tea' })).toBeVisible()
const toppingsButton = getByRole('button', { name: 'Topping : Pearls' })
expect(toppingsButton).toBeVisible()
await user.click(getByRole('button', { name: 'Clear Flavour' }))
await user.click(getByRole('button', { name: 'Check values' }))
await waitFor(() => {
expect(checkValues).toHaveBeenCalledWith({})
expect(toppingsButton).not.toBeInTheDocument()
})
})
})
describe('Multiple dependencies', () => {
it('handles complex dependencies', async () => {
type ValuesComplexDeps = {
coffee: string
milk: string
syrup: string
sugar: string
ice: string
}
const filters = [
{
id: 'coffee',
name: 'Coffee',
Component: (
),
},
{
id: 'milk',
name: 'Milk',
Component: (
),
isUsableWhen: (state) => state.coffee.value === 'latte',
},
{
id: 'syrup',
name: 'Syrup',
Component: (
),
isRemovable: true,
isUsableWhen: (state) => state.milk.value !== undefined && !state.sugar.isActive,
},
{
id: 'sugar',
name: 'Sugar',
Component: ,
isRemovable: true,
isUsableWhen: (state) => state.milk.value !== undefined && !state.syrup.isActive,
},
{
id: 'ice',
name: 'Ice',
Component: (
),
isUsableWhen: (state) => state.coffee.value !== undefined,
},
] satisfies Filters
const { queryByRole, getByRole } = render(
,
)
await waitForI18nContent()
const coffeeButton = getByRole('button', { name: 'Coffee' })
expect(coffeeButton).toBeVisible()
expect(queryByRole('button', { name: 'Milk' })).not.toBeInTheDocument()
expect(queryByRole('button', { name: 'Syrup' })).not.toBeInTheDocument()
expect(queryByRole('button', { name: 'Sugar' })).not.toBeInTheDocument()
expect(queryByRole('button', { name: 'Ice' })).not.toBeInTheDocument()
const addFiltersButton = getByRole('button', { name: 'Add filters' })
expect(addFiltersButton).toBeDisabled()
await user.click(coffeeButton)
await user.click(getByRole('option', { name: 'Long Black' }))
await waitFor(() => {
expect(coffeeButton).toHaveAccessibleName('Coffee : Long Black')
})
const iceButton = getByRole('button', { name: 'Ice' })
expect(iceButton).toBeVisible()
expect(addFiltersButton).toBeDisabled()
await user.click(coffeeButton)
await user.click(getByRole('option', { name: 'Latte' }))
await waitFor(() => {
expect(coffeeButton).toHaveAccessibleName('Coffee : Latte')
})
const milkButton = getByRole('button', { name: 'Milk' })
expect(milkButton).toBeVisible()
expect(addFiltersButton).toBeDisabled()
await user.click(milkButton)
await user.click(getByRole('option', { name: 'Oat' }))
await waitFor(() => {
expect(milkButton).toHaveAccessibleName('Milk : Oat')
})
expect(addFiltersButton).not.toBeDisabled()
await user.click(addFiltersButton)
const list = getByRole('list')
const menuOptionSugar = within(list).getByRole('button', {
name: 'Sugar',
})
const menuOptionSyrup = within(list).getByRole('button', {
name: 'Syrup',
})
expect(menuOptionSugar).toBeVisible()
expect(menuOptionSyrup).toBeVisible()
await user.click(menuOptionSugar)
await waitFor(() => {
expect(list).not.toBeInTheDocument()
})
const sugarButton = getByRole('button', { name: 'Sugar' })
expect(sugarButton).toBeVisible()
expect(addFiltersButton).toBeDisabled()
await user.click(getByRole('button', { name: 'Remove filter - Sugar' }))
await waitFor(() => {
expect(sugarButton).not.toBeInTheDocument()
})
expect(addFiltersButton).not.toBeDisabled()
})
})
})
describe('External events', () => {
it('allows updating the values via an external event', async () => {
const Wrapper = (): JSX.Element => {
type ExternalEventValues = {
flavour: string
}
const [values, setValues] = useState>({})
const filters = [
{
id: 'flavour',
name: 'Flavour',
Component: (
),
},
] satisfies Filters
return (
)
}
const { getByRole } = render()
await waitForI18nContent()
const flavourButton = getByRole('button', { name: 'Flavour' })
expect(flavourButton).toHaveAccessibleName('Flavour')
await user.click(getByRole('button', { name: 'Update Flavour to honey-milk-tea' }))
await waitFor(() => {
expect(flavourButton).toHaveAccessibleName('Flavour : Honey Milk Tea')
})
})
it('shows a removable filter when a value is set', async () => {
const Wrapper = (): JSX.Element => {
type ExternalEventValues = {
flavour: string
}
const [values, setValues] = useState>({})
const filters = [
{
id: 'flavour',
name: 'Flavour',
Component: (
),
isRemovable: true,
},
] satisfies Filters
return (
)
}
const { getByRole, queryByRole } = render()
await waitForI18nContent()
expect(queryByRole('button', { name: 'Flavour' })).not.toBeInTheDocument()
await user.click(getByRole('button', { name: 'Update Flavour to honey-milk-tea' }))
await waitFor(() => {
expect(getByRole('button', { name: 'Flavour : Honey Milk Tea' })).toBeVisible()
})
})
})
describe('Context use cases', () => {
describe('getActiveFilterValues()', () => {
type Items = { value: string; label: string }[]
type AsyncValues = {
city: string[]
hero: string[]
}
const MockFilterAsyncComponent = ({
id,
fetcher,
}: {
id: string
fetcher: (args: Partial) => Promise
}): JSX.Element => {
const [items, setItems] = useState([])
const { getActiveFilterValues } = useFilterBarContext()
const activeFilterVals = getActiveFilterValues()
useEffect(() => {
fetcher(activeFilterVals).then((fetchedItems) => {
if (JSON.stringify(fetchedItems) !== JSON.stringify(items)) {
setItems(fetchedItems)
}
})
}, [activeFilterVals, fetcher, items])
return (
{() => (
{({ allItems }) => (
{(item) => }
)}
)}
)
}
const fetchCityOptions = vi.fn((filterValues: Partial) => {
const isSupermanInFilterValue = filterValues.hero?.includes('superman')
const isBatmanInFilterValue = filterValues.hero?.includes('batman')
if (isBatmanInFilterValue && !isSupermanInFilterValue) {
return Promise.resolve([{ value: 'gotham', label: 'Gotham' }])
}
return Promise.resolve([
{ value: 'gotham', label: 'Gotham' },
{ value: 'metro', label: 'Metropolis' },
])
})
const fetchHeroOptions = vi.fn((filterValues: Partial) => {
const isGothamInFilterValue = filterValues.city?.includes('gotham')
const isMetroInFilterValue = filterValues.city?.includes('metro')
if (isGothamInFilterValue && !isMetroInFilterValue) {
return Promise.resolve([{ value: 'batman', label: 'Batman' }])
}
return Promise.resolve([
{ value: 'superman', label: 'Superman' },
{ value: 'batman', label: 'Batman' },
])
})
const config = [
{
id: 'city',
name: 'City',
Component: ,
},
{
id: 'hero',
name: 'Hero',
Component: ,
},
] satisfies Filters
it('can re-fetch options with all active filter values pulled off of the FilterBarContext', async () => {
const { getByRole, queryByRole } = render(
filters={config} defaultValues={{}} />,
)
await user.click(getByRole('button', { name: 'City' }))
await waitFor(() => {
expect(getByRole('option', { name: 'Gotham' })).toBeVisible()
expect(getByRole('option', { name: 'Metropolis' })).toBeVisible()
})
await user.click(getByRole('option', { name: 'Gotham' }))
// close city filter
await user.click(document.body)
await user.click(getByRole('button', { name: 'Hero' }))
await waitFor(() => {
expect(getByRole('option', { name: 'Batman' })).toBeVisible()
expect(queryByRole('option', { name: 'Superman' })).not.toBeInTheDocument()
})
await user.click(getByRole('option', { name: 'Batman' }))
await user.click(document.body)
await user.click(getByRole('button', { name: 'City : Gotham' }))
await waitFor(() => {
expect(getByRole('option', { name: 'Gotham' })).toBeVisible()
expect(queryByRole('option', { name: 'Metropolis' })).not.toBeInTheDocument()
})
})
})
describe('openFilter()', () => {
type CycleFilterValues = {
cycle: string
customDate: Date
}
const CycleFilter = ({ id }: { id?: string }): JSX.Element => {
const { openFilter } = useFilterBarContext()
return (
{
if (key === 'custom') openFilter('customDate')
}}
/>
)
}
const cycleFilters = [
{
id: 'cycle',
name: 'Cycle',
Component: ,
},
{
id: 'customDate',
name: 'Custom Date',
Component: ,
},
] satisfies Filters
it("opens the Custom Date filter when Cycle's 'custom' value is selected", async () => {
const { getByRole } = render( filters={cycleFilters} />)
await waitForI18nContent()
const customDateButton = getByRole('button', { name: 'Custom Date' })
expect(customDateButton).toHaveAttribute('aria-expanded', 'false')
await user.click(getByRole('button', { name: 'Cycle' }))
const customDateOption = getByRole('option', { name: 'Custom Date' })
await waitFor(() => {
expect(customDateOption).toBeVisible()
})
await user.click(customDateOption)
await waitFor(() => {
expect(customDateButton).toHaveAttribute('aria-expanded', 'true')
})
})
})
})
})