Files
ThothII/frontend/src/shell/SessionMenu.test.tsx
T
marcopanandClaude Opus 4.8 0a13f71b4d feat(resume): Resume in the kebab menu (A1) + harden the resume kickoff (A2)
A1 — SessionMenu gains a Resume item, gated to status!=="finalized" && !archived
(matching the backend's 409 read-only guard), wired in AppShell to doResume ->
POST /sessions/:id/resume. SessionMenu.test.tsx (3 tests); frontend 96/96, tsc clean.

A2 — diagnosis-first clean-room repro driving `pi --mode rpc` with the backend's
exact resume handshake shows the cold-start stall NO LONGER reproduces on pi
0.79.4 (8/8 chained into `tht session show` + `read SKILL.md` in-turn, fresh and
partway sessions). The earlier narrate-and-stop predates the pi upgrade.
Defense-in-depth anyway: RIPRENDI_KICKOFF hardened to force the in-turn tool call
(gate_resume_kickoff.test.js + live regression 2/2). Gate JS 34/34.

PROJECT_STATE open-item #1 (resume stall) flipped to RESOLVED; cross-model resume
robustness folded into workstream G.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:19:10 +02:00

50 lines
1.8 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import { SessionMenu } from "./SessionMenu";
import type { SessionSummary } from "../api/types";
const base: SessionSummary = {
id: "s1", status: "open", question: "q", summary: null,
created_at: "2026-01-01T00:00:00Z", updated_at: null, author: null,
name: null, group: null, archived: false,
};
function renderMenu(over: Partial<SessionSummary>, onResume = vi.fn()) {
render(
<SessionMenu
session={{ ...base, ...over }}
groups={[]}
onResume={onResume}
onView={vi.fn()}
onRename={vi.fn()}
onMove={vi.fn()}
onNewGroup={vi.fn()}
onArchiveToggle={vi.fn()}
onDelete={vi.fn()}
/>,
);
return onResume;
}
test("Resume shows for an open, non-archived session and calls onResume", async () => {
const onResume = renderMenu({ status: "open", archived: false });
await userEvent.click(screen.getByRole("button", { name: /session actions/i }));
await userEvent.click(await screen.findByText("Resume"));
expect(onResume).toHaveBeenCalledTimes(1);
});
test("Resume is hidden for a finalized session", async () => {
renderMenu({ status: "finalized" });
await userEvent.click(screen.getByRole("button", { name: /session actions/i }));
await screen.findByText("Split view"); // menu is open
expect(screen.queryByText("Resume")).not.toBeInTheDocument();
});
test("Resume is hidden for an archived session", async () => {
renderMenu({ status: "open", archived: true });
await userEvent.click(screen.getByRole("button", { name: /session actions/i }));
await screen.findByText("Split view"); // menu is open
expect(screen.queryByText("Resume")).not.toBeInTheDocument();
});