|
| 1 | +/** |
| 2 | + * Search dialog: filter chips. |
| 3 | + * |
| 4 | + * Exercises the Size chip (the simplest configured-state shape): default |
| 5 | + * state → open popover → set min value → confirm via input change → chip |
| 6 | + * shows the configured summary → × clears it. Also pins the Escape-scoped |
| 7 | + * close contract (`SearchFilterChips.svelte` documents this: Esc inside the |
| 8 | + * popover closes only the popover, not the dialog). |
| 9 | + * |
| 10 | + * The Modified and Search-in chips share the same popover primitive, so |
| 11 | + * covering one chip end-to-end is enough at the E2E tier; the per-chip shape |
| 12 | + * differences are pinned by the Vitest unit tests in |
| 13 | + * `SearchFilterChips.svelte.test.ts`. |
| 14 | + */ |
| 15 | + |
| 16 | +import { test, expect } from './fixtures.js' |
| 17 | +import { ensureAppReady, pollUntil, pressKey } from './helpers.js' |
| 18 | +import { ensureMcpClient } from '../e2e-shared/mcp-client.js' |
| 19 | +import { SEARCH_OVERLAY, closeSearchDialog, openSearchDialog } from './search-helpers.js' |
| 20 | + |
| 21 | +const SIZE_CHIP_DEFAULT = '.search-overlay .filter-chip[aria-label="Size"]' |
| 22 | +const SIZE_CHIP_CONFIGURED = '.search-overlay .filter-chip.is-configured' |
| 23 | +const SIZE_CHIP_CLEAR = '.search-overlay .filter-chip.is-configured .chip-clear' |
| 24 | +const FILTER_POPOVER = '.search-overlay .filter-chip-popover' |
| 25 | +const SIZE_MIN_INPUT = '.search-overlay .filter-chip-popover input[aria-label="Minimum size value"]' |
| 26 | + |
| 27 | +test.describe('Search dialog: filter chips', () => { |
| 28 | + test('Size chip: open popover, set min, confirm, clear via ×', async ({ tauriPage }) => { |
| 29 | + await ensureAppReady(tauriPage) |
| 30 | + await ensureMcpClient(tauriPage) |
| 31 | + await openSearchDialog(tauriPage) |
| 32 | + |
| 33 | + // Default state: chip exists, no `.is-configured` modifier. |
| 34 | + expect(await tauriPage.count(SIZE_CHIP_DEFAULT)).toBe(1) |
| 35 | + expect(await tauriPage.count(SIZE_CHIP_CONFIGURED)).toBe(0) |
| 36 | + |
| 37 | + // Click the Size chip → popover opens. The popover renders the comparator |
| 38 | + // select; setting it to `gte` from default `any` exposes the value input. |
| 39 | + await tauriPage.click(SIZE_CHIP_DEFAULT) |
| 40 | + await tauriPage.waitForSelector(FILTER_POPOVER, 2000) |
| 41 | + await tauriPage.evaluate(`(function(){ |
| 42 | + var sel = document.querySelector('.search-overlay .filter-chip-popover select[aria-label="Size comparator"]'); |
| 43 | + if (!sel) return; |
| 44 | + sel.value = 'gte'; |
| 45 | + sel.dispatchEvent(new Event('change', { bubbles: true })); |
| 46 | + })()`) |
| 47 | + |
| 48 | + // Fill the min-size input. The chip's configured summary reads back from |
| 49 | + // the same state, so we poll the chip class once the input lands. |
| 50 | + await tauriPage.waitForSelector(SIZE_MIN_INPUT, 2000) |
| 51 | + await tauriPage.evaluate(`(function(){ |
| 52 | + var el = document.querySelector(${JSON.stringify(SIZE_MIN_INPUT)}); |
| 53 | + if (!el) return; |
| 54 | + el.focus(); |
| 55 | + el.value = '100'; |
| 56 | + el.dispatchEvent(new Event('input', { bubbles: true })); |
| 57 | + })()`) |
| 58 | + const configured = await pollUntil(tauriPage, async () => (await tauriPage.count(SIZE_CHIP_CONFIGURED)) === 1, 2000) |
| 59 | + expect(configured).toBe(true) |
| 60 | + |
| 61 | + // Esc closes ONLY the popover (`SearchFilterChips.svelte` capture-phase |
| 62 | + // guard, see lib/search/CLAUDE.md). The dialog must stay open. |
| 63 | + await pressKey(tauriPage, 'Escape') |
| 64 | + const popoverGone = await pollUntil(tauriPage, async () => (await tauriPage.count(FILTER_POPOVER)) === 0, 2000) |
| 65 | + expect(popoverGone).toBe(true) |
| 66 | + expect(await tauriPage.count(SEARCH_OVERLAY)).toBe(1) |
| 67 | + |
| 68 | + // Click × to clear. The chip drops `is-configured` and the value vanishes |
| 69 | + // from the popover state (re-opening would show comparator `any`). |
| 70 | + await tauriPage.evaluate(`(function(){ |
| 71 | + var x = document.querySelector(${JSON.stringify(SIZE_CHIP_CLEAR)}); |
| 72 | + if (x) x.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); |
| 73 | + })()`) |
| 74 | + const cleared = await pollUntil(tauriPage, async () => (await tauriPage.count(SIZE_CHIP_CONFIGURED)) === 0, 2000) |
| 75 | + expect(cleared).toBe(true) |
| 76 | + |
| 77 | + await closeSearchDialog(tauriPage) |
| 78 | + }) |
| 79 | +}) |
0 commit comments