feat(frontend): collapsible group headers; resume/delete integration tests; English test name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
|
|||||||
import { http, HttpResponse } from "msw";
|
import { http, HttpResponse } from "msw";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { server } from "../test/msw";
|
import { server } from "../test/msw";
|
||||||
|
import { FakeEventSource } from "../test/fakeEventSource";
|
||||||
import { AppShell } from "./AppShell";
|
import { AppShell } from "./AppShell";
|
||||||
|
|
||||||
function wrap() {
|
function wrap() {
|
||||||
@@ -16,6 +17,8 @@ const LIST = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
FakeEventSource.instances = [];
|
||||||
|
(globalThis as any).EventSource = FakeEventSource;
|
||||||
server.use(
|
server.use(
|
||||||
http.get("http://localhost:8787/sessions", () => HttpResponse.json(LIST)),
|
http.get("http://localhost:8787/sessions", () => HttpResponse.json(LIST)),
|
||||||
http.get("http://localhost:8787/sessions/:id/documents", () => HttpResponse.json([
|
http.get("http://localhost:8787/sessions/:id/documents", () => HttpResponse.json([
|
||||||
@@ -43,3 +46,19 @@ test("Archive toggle reveals archived sessions", async () => {
|
|||||||
await userEvent.click(await screen.findByRole("button", { name: /archive/i }));
|
await userEvent.click(await screen.findByRole("button", { name: /archive/i }));
|
||||||
expect(await screen.findByText("Archiviata due")).toBeInTheDocument();
|
expect(await screen.findByText("Archiviata due")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Resume from the panel activates the session and closes the panel", async () => {
|
||||||
|
let resumed: string | null = null;
|
||||||
|
server.use(
|
||||||
|
http.post("http://localhost:8787/sessions/:id/resume", ({ params }) => {
|
||||||
|
resumed = params.id as string;
|
||||||
|
return new HttpResponse(null, { status: 204 });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
wrap();
|
||||||
|
await userEvent.click(await screen.findByText("Attiva uno"));
|
||||||
|
await screen.findByText("Domanda originale"); // panel open
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: /resume/i }));
|
||||||
|
await waitFor(() => expect(resumed).toBe("s1"));
|
||||||
|
await waitFor(() => expect(screen.queryByText("Domanda originale")).not.toBeInTheDocument()); // panel closed
|
||||||
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export function AppShell() {
|
|||||||
const [showArchive, setShowArchive] = useState(false);
|
const [showArchive, setShowArchive] = useState(false);
|
||||||
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
||||||
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
||||||
|
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
const groups = useMemo(
|
const groups = useMemo(
|
||||||
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
|
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
|
||||||
@@ -195,13 +196,23 @@ export function AppShell() {
|
|||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{groups.map((g) => (
|
{groups.map((g) => (
|
||||||
<div key={g}>
|
<div key={g}>
|
||||||
<p className="px-1 pb-1 text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground">{g}</p>
|
<button
|
||||||
<NavSessions
|
type="button"
|
||||||
sessions={activeList.filter((s) => s.group === g)}
|
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
|
||||||
activeSessionId={activeSessionId}
|
aria-expanded={!collapsedGroups[g]}
|
||||||
onOpenPanel={openPanel}
|
className="flex w-full items-center gap-1 px-1 pb-1 text-left text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground hover:text-foreground"
|
||||||
menuFor={menuFor}
|
>
|
||||||
/>
|
<span className="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
|
||||||
|
<span className="truncate">{g}</span>
|
||||||
|
</button>
|
||||||
|
{!collapsedGroups[g] && (
|
||||||
|
<NavSessions
|
||||||
|
sessions={activeList.filter((s) => s.group === g)}
|
||||||
|
activeSessionId={activeSessionId}
|
||||||
|
onOpenPanel={openPanel}
|
||||||
|
menuFor={menuFor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user