feat(frontend): add resizable activity split

This commit is contained in:
User
2026-07-15 15:04:15 +02:00
parent 30e76a968d
commit d177e1c0af
5 changed files with 313 additions and 13 deletions
@@ -1,4 +1,4 @@
import { act, render, screen, waitFor, within } from "@testing-library/react";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -6,6 +6,7 @@ import { server } from "../test/msw";
import { FakeEventSource } from "../test/fakeEventSource";
import { AppShell } from "./AppShell";
import { useSessionStore } from "../store/sessionStore";
import { ACTIVITY_PANEL_STORAGE_KEY } from "./useActivityPanelResize";
function wrap() {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
@@ -27,6 +28,7 @@ function deferred() {
}
beforeEach(() => {
localStorage.clear();
FakeEventSource.instances = [];
(globalThis as any).EventSource = FakeEventSource;
(globalThis as any).PointerEvent = MouseEvent;
@@ -779,21 +781,58 @@ test("renaming a group reassigns its members via setSessionGroup", async () => {
});
test("opening Model activity replaces the session rail with a 40/60 activity and chat layout", async () => {
test("opens an accessible resizable activity split and persists pointer width", async () => {
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 }));
expect(screen.getByText("Sessions")).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
expect(shell).toHaveAttribute("data-activity-layout", "open");
expect(screen.getByRole("heading", { name: "Model activity" }).closest("aside")).toHaveClass("w-2/5");
expect(screen.queryByText("Sessions")).not.toBeInTheDocument();
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");
expect(separator).toHaveAttribute("aria-valuemin", "288");
expect(separator).toHaveAttribute("aria-valuenow", "384");
await userEvent.click(screen.getByRole("button", { name: /hide model activity/i }));
expect(shell).toHaveAttribute("data-activity-layout", "closed");
expect(screen.getByText("Sessions")).toBeInTheDocument();
Object.assign(separator, {
setPointerCapture: vi.fn(),
releasePointerCapture: vi.fn(),
hasPointerCapture: vi.fn(() => true),
});
fireEvent.pointerDown(separator, { pointerId: 1, clientX: 384 });
fireEvent.pointerMove(separator, { pointerId: 1, clientX: 484 });
fireEvent.pointerUp(separator, { pointerId: 1, clientX: 484 });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("484px");
expect(localStorage.getItem(ACTIVITY_PANEL_STORAGE_KEY)).toBe("484");
expect(screen.getByTestId("conversation-column")).toHaveClass("flex-1");
});
test("resizes the activity split with keyboard and exposes responsive drawer classes", async () => {
localStorage.setItem(ACTIVITY_PANEL_STORAGE_KEY, "448");
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(screen.getByRole("button", { name: /show model activity/i }));
const shell = screen.getByTestId("app-shell");
const separator = screen.getByRole("separator", { name: /resize model activity/i });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("448px");
fireEvent.keyDown(separator, { key: "ArrowLeft" });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("432px");
fireEvent.keyDown(separator, { key: "ArrowRight", shiftKey: true });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("480px");
fireEvent.keyDown(separator, { key: "Home" });
expect(shell.style.getPropertyValue("--activity-panel-width")).toBe("288px");
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)]",
);
expect(separator).toHaveClass("touch-none", "cursor-col-resize");
});