import { MantineProvider } from "@mantine/core";
import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
import { afterEach, expect, test, vi } from "vitest";
import { fixtureTask } from "../../ui/fixtures";
import { NextTaskDetail } from "./NextTaskDetail";
afterEach(cleanup);
test("details show ordered PRs, one access alert per repo, and useful collapsed fields", () => {
const task = { ...fixtureTask(), replyExcerpt: "The **update** is ready.", relatedGitHubLinks: [], inputSource: "ordinary" as const,
observed: { archive: false, source: "vscode", lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false, latestTurnDurationMs: 480000, recordedChatDurationMs: 1200000, directChildCount: 155 },
detailPullRequests: [
{ url: "https://github.com/example/repo/pull/3", title: "Latest change", state: "open" as const, checks: "passed" as const },
{ url: "https://github.com/example/repo/pull/2", title: "Older change", state: "unknown" as const, checks: "unknown" as const, accessIssue: "denied" as const },
{ url: "https://github.com/example/other/pull/1", title: "Other repo", state: "unknown" as const, checks: "unknown" as const, accessIssue: "denied" as const },
] };
render( {}} onOpenCodex={() => {}} />);
const prs = screen.getByRole("region", { name: "Pull requests" });
expect(within(prs).getAllByRole("link").map(link => link.textContent)).toEqual(["#3 Latest change", "Grant access", "#2 Older change", "#1 Other repo", "Grant access"]);
expect(screen.getByRole("region", { name: "Activity" }).compareDocumentPosition(prs) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(screen.getByText("Latest turn worked for 8m")).toBeVisible();
expect(screen.getByText("Total chat worked for 20m")).toBeVisible();
expect(within(prs).queryByText("CI passed")).toBeNull();
expect(screen.getByRole("dialog", { name: task.title })).toBeVisible();
expect(screen.getAllByText(task.title)).toHaveLength(1);
expect(screen.getByText("155 subagents")).toBeVisible();
expect(screen.queryByText("Latest saved reply (excerpt)")).toBeNull();
const details = screen.getByText("Technical details").closest("details")!;
expect(details.open).toBe(false);
fireEvent.click(screen.getByText("Technical details"));
expect(within(details).getByText("Desktop")).toBeVisible();
expect(within(details).getByText("Human")).toBeVisible();
expect(screen.queryByRole("button", {name: "Chat actions"})).toBeNull();
expect(screen.queryByRole("button", {name: "Copy Chat ID"})).toBeNull();
});
test("a missing older PR keeps its warning after that PR rather than the accessible latest PR", () => {
const task = { ...fixtureTask(), relatedGitHubLinks: [], detailPullRequests: [
{ url: "https://github.com/example/repo/pull/3", title: "Accessible latest", state: "open" as const, checks: "passed" as const },
{ url: "https://github.com/example/repo/pull/2", title: "Missing older", state: "unknown" as const, checks: "unknown" as const, accessIssue: "not_found" as const },
] };
render( {}} onOpenCodex={() => {}} />);
expect(within(screen.getByRole("region", { name: "Pull requests" })).getAllByRole("link").map(link => link.textContent)).toEqual(["#3 Accessible latest", "#2 Missing older", "Check access"]);
});
test("running details use Worked for rather than the finished-turn label", () => {
const task = { ...fixtureTask(), observed: { archive: false, lastTurnEvent: "inProgress", lastTurnEventAt: new Date(Date.now()-120000).toISOString(), recentFileActivity: true, recordedChatDurationMs: 180000 } };
render({}} onOpenCodex={()=>{}} />);
expect(screen.getByText("Worked for 2m")).toBeVisible();
expect(screen.getByText("Total chat worked for 5m")).toBeVisible();
expect(screen.queryByText(/Latest turn worked for/)).toBeNull();
});
test("usage keeps later-turn tokens visible when historical cost is partial", () => {
const latest = {tokens: {input_tokens: 100, cached_input_tokens: 20, cache_write_input_tokens: 0, output_tokens: 10, reasoning_output_tokens: 5, total_tokens: 110}, costUsd: 0.2, samples: 1, partial: false, costPartial: false};
const task = {...fixtureTask(), status: "needs_input" as const, sessionUsage: {latest, total: {...latest,tokens: {...latest.tokens,total_tokens: 10000},costUsd: 3.2, partial: true,costPartial: true}, subagents: 3, pricingDate: "2026-10-10"}};
render({}} onOpenCodex={()=>{}} />);
const usage=screen.getByRole("region",{name:"Usage"});
expect(within(usage).getByText("Total chat")).toBeVisible();
expect(within(usage).getByRole("link", {name: "OpenAI API pricing"})).toHaveAttribute("href", "https://developers.openai.com/api/docs/pricing");
expect(within(usage).queryByText(/Includes 3 subagents/)).toBeNull();
expect(within(usage).getByText("110")).toBeVisible();
expect(within(usage).getByText("≥10.00K")).toBeVisible();
expect(within(usage).getByRole("columnheader", {name:"Tokens 2"})).toBeVisible();
expect(within(usage).getByRole("columnheader", {name:"API-equivalent cost 1,2"})).toBeVisible();
expect(within(usage).getByText("$0.20")).toBeVisible();
expect(within(usage).getByText("≥$3.20")).toBeVisible();
expect(within(usage).getByText(/Incomplete estimate:/)).toBeVisible();
});
test("detail header keeps Open chat and Done without the old action menu", () => {
const open=vi.fn();
render({}} onOpenCodex={open} extraActions={} />);
fireEvent.click(screen.getByRole("button", {name: /^Open chat for/}));
expect(open).toHaveBeenCalledTimes(1);
expect(screen.getByRole("button", {name: "Mark Done"})).toBeVisible();
expect(screen.queryByRole("button", {name: "Chat actions"})).toBeNull();
});
test("unpriced usage marks the cost label with the incomplete footnote", () => {
const value={tokens:{input_tokens:10,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:1,reasoning_output_tokens:0,total_tokens:11},samples:1,costUsd:0,costPartial:true,partial:false};
const task={...fixtureTask(),sessionUsage:{latest:value,total:value,subagents:0,pricingDate:"2026-10-10"}};
render({}} onOpenCodex={()=>{}} />);
expect(within(screen.getByRole("region",{name:"Usage"})).getAllByText("—")).toHaveLength(2);
});
test("Usage stays visible while loading and when data is unavailable", () => {
const props={task:fixtureTask(),opened:true,busy:false,error:null,onClose:()=>{},onOpenCodex:()=>{}};
const view=render();
const section=screen.getByRole("region",{name:"Usage"});
expect(within(section).getByRole("heading",{name:"Usage"})).toBeVisible();
expect(within(section).getByRole("status")).toHaveTextContent("Loading…");
view.rerender();
expect(screen.getByRole("region",{name:"Usage"})).toBe(section);
expect(within(section).getByRole("status")).toHaveTextContent("Usage unavailable.");
const value={tokens:{input_tokens:100,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:10,reasoning_output_tokens:0,total_tokens:110},samples:1,costUsd:0.1,costPartial:false,partial:false};
view.rerender();
expect(screen.getByRole("region",{name:"Usage"})).toBe(section);
expect(within(section).getByRole("table")).toBeVisible();
expect(within(section).queryByRole("status")).toBeNull();
});
test("a resolved usage read with zero samples is unavailable", () => {
const empty={tokens:{input_tokens:0,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:0,reasoning_output_tokens:0,total_tokens:0},samples:0,costUsd:0,costPartial:true,partial:true};
render({}} onOpenCodex={()=>{}} />);
const usage=screen.getByRole("region",{name:"Usage"});expect(within(usage).getByRole("status")).toHaveTextContent("Usage unavailable.");expect(within(usage).queryByRole("table")).toBeNull();
});
test('Needs answer adds an open-chat action without replacing the saved prompt or response', () => {
const open=vi.fn();
const task={...fixtureTask(),needsAnswer:true,pendingQuestions:[{id:'question',title:'May I retry the scan?',options:['Retry once','Stop']}],promptText:'Run the scan.',replyText:'The connection failed. Approval requested.'};
render({}} onOpenCodex={open}/>);
expect(screen.getByText('Needs answer')).toBeInTheDocument();
const questions=screen.getByRole('region',{name:'Pending questions'});
expect(within(questions).getByText('May I retry the scan?')).toBeVisible();
expect(within(questions).getAllByRole('listitem').map(item=>item.textContent)).toEqual(['1Retry once','2Stop']);
expect(within(questions).getByRole('button',{name:'Open chat to answer'})).toBeVisible();
expect(screen.getByText('Run the scan.')).toBeInTheDocument();
expect(screen.getByText('The connection failed. Approval requested.')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button',{name:'Open chat to answer'}));expect(open).toHaveBeenCalledTimes(1);
});