fix(frontend): harden activity split responsiveness

This commit is contained in:
User
2026-07-15 15:52:22 +02:00
parent f0ef2654df
commit 1f540fcb78
6 changed files with 160 additions and 18 deletions
@@ -21,6 +21,23 @@ const LIST = [
const resumeResult = (id: string, alreadyActive = false) =>
HttpResponse.json({ id, alreadyActive });
class ControlledResizeObserver {
static instances: ControlledResizeObserver[] = [];
constructor(private readonly callback: ResizeObserverCallback) {
ControlledResizeObserver.instances.push(this);
}
observe = vi.fn();
disconnect = vi.fn();
trigger() {
this.callback([], this as unknown as ResizeObserver);
}
}
function setContainerWidth(element: HTMLElement, width: number) {
Object.defineProperty(element, "clientWidth", { configurable: true, value: width });
act(() => ControlledResizeObserver.instances.at(-1)?.trigger());
}
function deferred() {
let resolve!: () => void;
const promise = new Promise<void>((onResolve) => { resolve = onResolve; });
@@ -30,8 +47,11 @@ function deferred() {
beforeEach(() => {
localStorage.clear();
FakeEventSource.instances = [];
ControlledResizeObserver.instances = [];
(globalThis as any).EventSource = FakeEventSource;
(globalThis as any).PointerEvent = MouseEvent;
(globalThis as any).ResizeObserver = ControlledResizeObserver;
window.matchMedia = vi.fn().mockReturnValue({ matches: true, addEventListener: vi.fn(), removeEventListener: vi.fn() });
useSessionStore.getState().resetSession();
server.use(
http.get("http://localhost:8787/sessions", () => HttpResponse.json(LIST)),
@@ -789,6 +809,7 @@ test("opens an accessible resizable activity split and persists pointer width",
await userEvent.click(screen.getByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
setContainerWidth(shell, 1200);
const separator = screen.getByRole("separator", { name: /resize model activity/i });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("384px");
expect(separator).toHaveAttribute("aria-orientation", "vertical");
@@ -818,6 +839,7 @@ test("resizes the activity split with keyboard and exposes responsive drawer cla
await userEvent.click(screen.getByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
setContainerWidth(shell, 1200);
const separator = screen.getByRole("separator", { name: /resize model activity/i });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("448px");
fireEvent.keyDown(separator, { key: "ArrowLeft" });
@@ -829,10 +851,53 @@ test("resizes the activity split with keyboard and exposes responsive drawer cla
const panel = screen.getByRole("heading", { name: "Model activity" }).closest("aside");
expect(panel).toHaveClass(
"fixed",
"w-[min(90vw,24rem)]",
"lg:static",
"lg:w-[var(--activity-panel-width)]",
"static",
"w-[var(--activity-panel-width)]",
);
expect(separator).toHaveClass("touch-none", "cursor-col-resize");
});
test("uses the measured app shell for the desktop activity split", async () => {
localStorage.setItem(ACTIVITY_PANEL_STORAGE_KEY, "576");
server.use(http.post("http://localhost:8787/sessions/:id/resume", () => resumeResult("s1")));
wrap();
await userEvent.click(await screen.findByText("Attiva uno"));
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
await userEvent.click(await screen.findByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
setContainerWidth(shell, 1200);
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("576px");
expect(screen.getByRole("separator", { name: /resize model activity/i })).toBeInTheDocument();
setContainerWidth(shell, 1024);
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("512px");
setContainerWidth(shell, 799);
expect(screen.queryByRole("separator", { name: /resize model activity/i })).not.toBeInTheDocument();
expect(screen.getByTestId("conversation-column")).toHaveClass("flex-1");
expect(screen.getByRole("heading", { name: "Model activity" }).closest("aside")).toHaveClass("fixed");
});
test("cancels an active resize when the measured shell becomes too narrow", async () => {
localStorage.setItem(ACTIVITY_PANEL_STORAGE_KEY, "576");
server.use(http.post("http://localhost:8787/sessions/:id/resume", () => resumeResult("s1")));
wrap();
await userEvent.click(await screen.findByText("Attiva uno"));
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
await userEvent.click(await screen.findByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
setContainerWidth(shell, 1200);
const separator = screen.getByRole("separator", { name: /resize model activity/i });
Object.assign(separator, {
setPointerCapture: vi.fn(), releasePointerCapture: vi.fn(), hasPointerCapture: vi.fn(() => true),
});
fireEvent.pointerDown(separator, { pointerId: 1, clientX: 576 });
fireEvent.pointerMove(separator, { pointerId: 1, clientX: 400 });
setContainerWidth(shell, 799);
fireEvent.pointerUp(separator, { pointerId: 1, clientX: 300 });
expect(localStorage.getItem(ACTIVITY_PANEL_STORAGE_KEY)).toBe("576");
expect(shell).toHaveAttribute("data-activity-resizing", "false");
});