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 e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
35 changes: 34 additions & 1 deletion e2e/home.spec.ts
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down Expand Up @@ -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();
});
});
28 changes: 18 additions & 10 deletions src/components/PrFilters/PrFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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]);

Expand Down
Loading