From fcc87e1cb5a9608fb0bf21081fc6d3e124b061b6 Mon Sep 17 00:00:00 2001 From: RonStrauss Date: Tue, 26 May 2026 20:46:19 +0300 Subject: [PATCH] fix issue --- e2e/helpers.ts | 28 +++++++++++++++++++++ e2e/home.spec.ts | 35 +++++++++++++++++++++++++- src/components/PrFilters/PrFilters.tsx | 28 +++++++++++++-------- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 2ba2732..beeb5e5 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -34,6 +34,34 @@ export async function openFilters(page: Page) { await expect(page.getByTestId("filters-panel")).toBeVisible(); } +/** Simulate a pointer click outside an open Radix select (Playwright clicks are blocked). */ +export async function pointerDownOutsideSelect(page: Page) { + await page.evaluate(() => { + const init: PointerEventInit = { + bubbles: true, + cancelable: true, + composed: true, + pointerId: 1, + pointerType: "mouse", + isPrimary: true, + button: 0, + buttons: 1, + clientX: 8, + clientY: 8, + }; + document.dispatchEvent(new PointerEvent("pointerdown", init)); + document.dispatchEvent(new PointerEvent("pointerup", { ...init, buttons: 0 })); + }); +} + +/** Open a filter select dropdown without choosing an option */ +export async function openFilterSelect(page: Page, fieldId: string) { + const panel = page.getByTestId("filters-panel"); + await expect(panel).toBeVisible(); + await panel.locator(`#${fieldId}`).click(); + await expect(page.getByRole("listbox")).toBeVisible(); +} + /** Pick an option from a filter select by field id */ export async function pickFilterSelect( page: Page, diff --git a/e2e/home.spec.ts b/e2e/home.spec.ts index c040bd6..a3609a9 100644 --- a/e2e/home.spec.ts +++ b/e2e/home.spec.ts @@ -1,5 +1,12 @@ import { test, expect } from "@playwright/test"; -import { gotoApp, openFilters, pickFilterSelect, resetMockData } from "./helpers"; +import { + gotoApp, + openFilterSelect, + openFilters, + pickFilterSelect, + pointerDownOutsideSelect, + resetMockData, +} from "./helpers"; test.describe("Home — PR list", () => { test.beforeEach(async ({ page }) => { @@ -51,4 +58,30 @@ test.describe("Home — PR list", () => { await expect(page.getByText("Deadlift")).toBeVisible(); await expect(page.getByText("Back Squat")).toBeVisible(); }); + + test("Escape closes open select but keeps filters panel", async ({ page }) => { + await openFilters(page); + await openFilterSelect(page, "filter-exercise"); + await page.keyboard.press("Escape"); + await expect(page.getByTestId("filters-panel")).toBeVisible(); + await expect(page.getByRole("listbox")).not.toBeVisible(); + }); + + test("clicking outside filters while select is open closes select but keeps panel", async ({ + page, + }) => { + await openFilters(page); + await openFilterSelect(page, "filter-exercise"); + await pointerDownOutsideSelect(page); + await expect(page.getByTestId("filters-panel")).toBeVisible(); + await expect(page.getByRole("listbox")).not.toBeVisible(); + }); + + test("clicking outside filters with select closed closes the panel", async ({ + page, + }) => { + await openFilters(page); + await page.locator("h1").first().click(); + await expect(page.getByTestId("filters-panel")).not.toBeVisible(); + }); }); diff --git a/src/components/PrFilters/PrFilters.tsx b/src/components/PrFilters/PrFilters.tsx index 0aa8748..6ca3b63 100644 --- a/src/components/PrFilters/PrFilters.tsx +++ b/src/components/PrFilters/PrFilters.tsx @@ -11,6 +11,18 @@ import { } from "@/lib/prFilters"; import styles from "./PrFilters.module.css"; +function isFilterSelectOpen(): boolean { + return Boolean(document.querySelector('[role="listbox"][data-state="open"]')); +} + +function isSelectLayerTarget(target: Element): boolean { + return Boolean( + target.closest( + '[data-radix-popper-content-wrapper], [role="listbox"], [role="option"]' + ) + ); +} + type Props = { filters: PrFiltersState; exercises: string[]; @@ -63,25 +75,21 @@ export function PrFilters({ const target = e.target; if (!(target instanceof Element)) return; if (rootRef.current?.contains(target)) return; - if ( - target.closest( - '[data-radix-popper-content-wrapper], [role="listbox"], [role="option"]' - ) - ) { - return; - } + if (isSelectLayerTarget(target)) return; + // Select is portaled; Radix closes it first — keep the filters panel open. + if (isFilterSelectOpen()) return; onOpenChange(false); } function onKeyDown(e: KeyboardEvent) { if (e.key !== "Escape") return; - if (document.querySelector('[role="listbox"]')) return; + if (isFilterSelectOpen()) return; onOpenChange(false); } document.addEventListener("pointerdown", onPointerDownOutside, true); - document.addEventListener("keydown", onKeyDown); + document.addEventListener("keydown", onKeyDown, true); return () => { document.removeEventListener("pointerdown", onPointerDownOutside, true); - document.removeEventListener("keydown", onKeyDown); + document.removeEventListener("keydown", onKeyDown, true); }; }, [open, onOpenChange]);