fix(frontend): harden activity split responsiveness
This commit is contained in:
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user