Файли
openidea/src/workbench/nativeTitlebar.test.tsx
T
l0ng-ai ad92b1577f fix(window): align macOS traffic lights with IDE zoom (#574)
IDE zoom changed the toolbar height while the macOS window buttons stayed at a fixed vertical position. The buttons are now centred in the title bar using its measured height and the zoom, and repositioned after AppKit layout updates. Zooming out keeps enough room for them so they do not cover the project icon, and compact mode follows the same alignment.

Windows align from the saved zoom and compact setting when they are built, so the buttons do not move once the page loads. The height uses the zoom the webview applies, so an out-of-range setting cannot misplace them.
2026-10-31 22:38:16 +08:00

86 рядки
2.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { usePlatform } from "@/platform/platformInfo";
import { useProject } from "@/platform/project";
import type { Settings } from "@/platform/ipc";
import { useNativeTitlebar } from "./nativeTitlebar";
const { setHeight } = vi.hoisted(() => ({ setHeight: vi.fn().mockResolvedValue({ status: "ok" }) }));
vi.mock("@/platform/ipc", async (original) => {
const ipc = await original<typeof import("@/platform/ipc")>();
return { ...ipc, commands: { ...ipc.commands, windowSetTitlebarHeight: setHeight } };
});
const platform = usePlatform.getState();
const project = useProject.getState();
let resize: () => void;
const disconnect = vi.fn();
beforeEach(() => {
setHeight.mockClear();
disconnect.mockClear();
usePlatform.setState({ overlayTitleBar: true });
vi.stubGlobal(
"ResizeObserver",
class {
constructor(callback: () => void) {
resize = callback;
}
observe() {}
disconnect = disconnect;
},
);
});
afterEach(() => {
usePlatform.setState(platform);
useProject.setState(project);
vi.unstubAllGlobals();
});
function zoom(percent: number) {
useProject.setState({ settings: { appearance: { zoom: percent } } as Settings });
}
function titlebar(height: number) {
const bar = document.createElement("header");
const rect = vi.spyOn(bar, "getBoundingClientRect").mockReturnValue(new DOMRect(0, 0, 800, height));
return { bar, rect };
}
describe("native title bar alignment", () => {
it.each([50, 100, 125, 150, 200, 300])("converts CSS height to AppKit points at %s%%", (percent) => {
zoom(percent);
const { bar } = titlebar(40);
renderHook(() => useNativeTitlebar(bar));
expect(setHeight).toHaveBeenLastCalledWith((40 * percent) / 100);
});
it("uses the zoom the webview applies when the setting is out of range", () => {
zoom(500);
const { bar } = titlebar(40);
renderHook(() => useNativeTitlebar(bar));
expect(setHeight).toHaveBeenLastCalledWith(120);
});
it("updates when zoom or compact mode changes, and disconnects on unmount", () => {
zoom(100);
const { bar, rect } = titlebar(40);
const { unmount } = renderHook(() => useNativeTitlebar(bar));
act(() => zoom(150));
expect(setHeight).toHaveBeenLastCalledWith(60);
rect.mockReturnValue(new DOMRect(0, 0, 800, 32));
act(() => resize());
expect(setHeight).toHaveBeenLastCalledWith(48);
unmount();
expect(disconnect).toHaveBeenCalledTimes(2);
});
it("leaves windows without native overlay buttons alone", () => {
usePlatform.setState({ overlayTitleBar: false });
const { bar } = titlebar(40);
renderHook(() => useNativeTitlebar(bar));
expect(setHeight).not.toHaveBeenCalled();
});
});