Files
paseo/packages/app/src/components/ui/menu/menu-context.test.tsx
T
Mohamed Boudra bf6cd09d6c fix(app): run menu selections that close the menu on iOS
Selecting an item parked its action in a ref and waited for the Modal's
onDismiss to flush it. MenuOverlay returns null as soon as the menu closes, so
the Modal unmounted and onDismiss never fired: every close-on-select action was
dropped. Creating a terminal from the compact workspace menu did nothing at all.

Wait on a timer instead. The grace period was always about giving UIKit time to
tear the surface down before a native presenter launches, and a callback fired
from inside a subtree being removed is racing its own removal. The sheet had no
flush at all, so it was broken the same way for a different reason.
2026-08-04 12:03:00 +02:00

64 lines
2.0 KiB
TypeScript

/**
* @vitest-environment jsdom
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { Platform } from "react-native";
import { IOS_TEARDOWN_GRACE_MS, useMenuState } from "./menu-context";
/**
* `Platform.OS` is a plain property on the react-native-web double, so the iOS branch is
* reachable by writing to it. The grace period only exists on iOS, which is exactly the platform
* whose selections used to be dropped.
*/
const originalPlatform = Platform.OS;
function setPlatform(os: string) {
Object.assign(Platform, { OS: os });
}
beforeEach(() => vi.useFakeTimers());
afterEach(() => {
vi.useRealTimers();
setPlatform(originalPlatform);
});
describe("useMenuState selectItem on iOS", () => {
beforeEach(() => setPlatform("ios"));
it("runs a closing item's action once the surface has had time to go away", () => {
const onSelect = vi.fn();
const { result } = renderHook(() => useMenuState({ defaultOpen: true }));
act(() => result.current.selectItem(onSelect, true));
expect(result.current.open).toBe(false);
expect(onSelect).not.toHaveBeenCalled();
act(() => vi.advanceTimersByTime(IOS_TEARDOWN_GRACE_MS));
expect(onSelect).toHaveBeenCalledTimes(1);
});
it("leaves the menu open for an item that does not close it", () => {
const onSelect = vi.fn();
const { result } = renderHook(() => useMenuState({ defaultOpen: true }));
act(() => result.current.selectItem(onSelect, false));
expect(result.current.open).toBe(true);
expect(onSelect).toHaveBeenCalledTimes(1);
});
});
describe("useMenuState selectItem elsewhere", () => {
beforeEach(() => setPlatform("android"));
it("runs a closing item's action immediately", () => {
const onSelect = vi.fn();
const { result } = renderHook(() => useMenuState({ defaultOpen: true }));
act(() => result.current.selectItem(onSelect, true));
expect(result.current.open).toBe(false);
expect(onSelect).toHaveBeenCalledTimes(1);
});
});