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
28 changes: 28 additions & 0 deletions suite/apps/tableaux/utils/guestFilters.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,31 @@ describe('the Unassigned chip', () => {
expect(matchesFilters(declined, ['unassigned'])).toBe(false)
})
})

// Chips in one category widen the list; chips in different categories narrow
// it. A guest has one diet and one side, so ANDing two chips from the same
// category could only ever show nobody, or only the guests on both sides.
describe('combining chips', () => {
const veggie = { dietary: 'vegetarian', side: 'a', assignedTableId: null, rsvpStatus: 'confirmed' }
const vegan = { dietary: 'vegan', side: 'b', assignedTableId: 't1', rsvpStatus: 'confirmed' }
const neither = { dietary: '', side: 'both', assignedTableId: null, rsvpStatus: 'confirmed' }

it('shows vegetarians and vegans when both diet chips are on', () => {
expect(matchesFilters(veggie, ['vegetarian', 'vegan'])).toBe(true)
expect(matchesFilters(vegan, ['vegetarian', 'vegan'])).toBe(true)
expect(matchesFilters(neither, ['vegetarian', 'vegan'])).toBe(false)
})

it("shows everyone from either side when both partners' chips are on", () => {
expect(matchesFilters(veggie, ['a', 'b'])).toBe(true)
expect(matchesFilters(vegan, ['a', 'b'])).toBe(true)
expect(matchesFilters(neither, ['a', 'b'])).toBe(true)
})

it('still narrows across categories', () => {
expect(matchesFilters(veggie, ['vegan', 'a'])).toBe(false)
expect(matchesFilters(vegan, ['vegan', 'b'])).toBe(true)
expect(matchesFilters(vegan, ['vegetarian', 'vegan', 'unassigned'])).toBe(false)
expect(matchesFilters(veggie, ['vegetarian', 'vegan', 'unassigned'])).toBe(true)
})
})
31 changes: 24 additions & 7 deletions suite/apps/tableaux/utils/guestFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,32 @@ const PREDICATES: Record<FilterKey, (guest: Guest) => boolean> = {
notes: (g) => !!(g.notes && g.notes.trim()),
}

// TODO(ux-audit): matchesFilters ANDs every active filter (below), which
// produces silent, misleading results for two real combos: Vegetarian+Vegan
// ticked together is a guaranteed-empty result (dietary is a single value,
// can never match both); and both side chips together only matches
// side==='both', not the union a user would expect from ticking two side
// chips. See tmp/ux-audit.md #G7.
/**
* Chips in one category widen the list, and chips in different categories
* narrow it: Vegetarian + Vegan is everyone on either diet, and Vegan + Alex's
* is Alex's vegans. A guest has one diet and one side, so ANDing two chips of
* the same category could only ever show nobody, or just the guests on both
* sides.
*/
const CATEGORY: Record<FilterKey, string> = {
unassigned: 'seat',
a: 'side',
b: 'side',
vegetarian: 'diet',
vegan: 'diet',
'gluten-free': 'diet',
notes: 'notes',
}

export function matchesFilters(guest: Guest, filters: readonly string[] | null | undefined): boolean {
if (!filters || filters.length === 0) return true
return filters.every((f) => (f in PREDICATES ? PREDICATES[f as FilterKey](guest) : true))
const byCategory = new Map<string, FilterKey[]>()
for (const f of filters) {
if (!(f in PREDICATES)) continue
const key = f as FilterKey
byCategory.set(CATEGORY[key], [...(byCategory.get(CATEGORY[key]) ?? []), key])
}
return [...byCategory.values()].every((keys) => keys.some((key) => PREDICATES[key](guest)))
}

export function matchesSearch(guest: Guest, group: Pick<Group, 'name'> | null | undefined, query: string): boolean {
Expand Down
Loading