// @vitest-environment jsdom // // "Manage views…" opens a dialog from a menu row, so it must close the menu FIRST and open the dialog // on a fresh task (`closeThenAct`): the menu's focus return, queued as it unmounts, otherwise takes the // dialog's focus back to the trigger behind its scrim — measured in Chrome on ActionMenu (E11 S2), and // this row was the sibling that fix missed (deep-review E11 round 1). jsdom does not run that focus // race, so the ORDER is what is pinned: the menu is gone before the dialog is asked to open. import { beforeAll, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen } from "@testing-library/react"; import { FloatingTree } from "@floating-ui/react"; const openModal = vi.fn(); vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key }) }; }); vi.mock("./savedViewsContext", () => ({ useSavedViews: () => ({ views: [], activeViewId: null, apply: vi.fn(), openModal }), })); import SavedViewsPopover from "./SavedViewsPopover"; beforeAll(() => { class Stub { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver; globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver; }); describe("SavedViewsPopover · Manage views…", () => { it("closes the menu first, then opens the dialog once", async () => { render( , ); fireEvent.click(screen.getByRole("button", { name: "Saved views" })); // floating-ui never positions in jsdom, so the surface stays visibility:hidden and the row has no // accessible name to query by: find it by its text. fireEvent.click(screen.getByText("views.manage")); expect(screen.queryByRole("menuitem", { hidden: true })).toBeNull(); expect(openModal).not.toHaveBeenCalled(); await vi.waitFor(() => expect(openModal).toHaveBeenCalledTimes(1)); }); }); // E28: the toolbar chip's glyph sat INSIDE the value text, and an is display:block under the // preflight, so the chip broke onto two lines (the bookmark above "gaming ▾"). The glyph and the // modified dot ride in the trigger's own slots; the value is a row. describe("SavedViewsPopover · toolbar chip", () => { it("puts the glyph in the trigger's icon slot, not inside the value", () => { render( } trigger={gaming} ariaLabel="View chip" /> , ); const button = screen.getByRole("button", { name: "View chip" }); expect(screen.getByTestId("glyph").parentElement).toBe(button); expect(screen.getByText("gaming").parentElement!.className).toContain("flex"); }); });