import { fireEvent, render, screen } from "@testing-library/react";
import { createRef, type ReactNode } from "react";
import { describe, expect, it, vi } from "vitest";
import type { AgentTranscriptVirtualScrollController } from "../../../shared/agentConversation/components/AgentTranscriptView";
import { AgentGUIDetailTimeline } from "./AgentGUIDetailTimeline";
const { timelineRenderSpy } = vi.hoisted(() => ({
timelineRenderSpy: vi.fn()
}));
vi.mock("./AgentGUIConversationTimelinePane", () => ({
AgentGUIConversationTimelinePane: (props: {
turnAttachments?: readonly { id: string; content: ReactNode }[];
}) => {
timelineRenderSpy(props);
return (
{props.turnAttachments?.map((attachment) => (
{attachment.content}
))}
);
}
}));
describe("AgentGUIDetailTimeline render budget", () => {
it("does not rerender the active timeline for a parent-only draft update", () => {
const stableProps = {
availableSkills: [],
conversation: null,
conversationFlowEmpty: ,
conversationFlowLabels: {
thinkingLabel: "Thinking",
toolCallsLabel: (count: number) => `${count}`,
processing: "Processing",
turnSummary: "Summary",
userMessageLocator: "User"
},
hasActiveConversation: true,
followEndMode: "following" as const,
homeContent: null,
isLoadingOlderMessages: false,
isVisible: true,
isTimelineScrolledToTop: true,
labels: {
loadingConversation: "Loading",
continuedFromTask: "Continued from task",
selectionAddToConversation: "Add to conversation",
selectionAskInSide: "Ask in Side chat"
},
onAddSelectionToConversation: vi.fn(),
showTimelineSkeleton: false,
showUnavailableChatEmpty: false,
timelineContentRef: createRef(),
timelineRef: createRef(),
virtualScrollControllerRef:
createRef(),
workspaceAppIcons: []
};
const Parent = ({ draft }: { draft: string }) => {
void draft;
return ;
};
const rendered = render();
expect(timelineRenderSpy).toHaveBeenCalledOnce();
rendered.rerender();
expect(timelineRenderSpy).toHaveBeenCalledOnce();
});
it("projects durable fork lineage as a target-Turn attachment", () => {
const onOpenForkSourceSession = vi.fn();
render(
}
conversationFlowLabels={{
thinkingLabel: "Thinking",
toolCallsLabel: (count) => `${count}`,
processing: "Processing",
turnSummary: "Summary",
userMessageLocator: "User"
}}
hasActiveConversation
followEndMode="following"
forkedFrom={{
sourceAgentSessionId: "source-session",
sourceTurnId: "source-turn",
targetTurnId: "target-turn",
operationId: "fork-operation",
forkedAtUnixMs: 100
}}
homeContent={null}
isLoadingOlderMessages={false}
isVisible
isTimelineScrolledToTop
labels={{
loadingConversation: "Loading",
continuedFromTask: "Continued from task",
selectionAddToConversation: "Add to conversation",
selectionAskInSide: "Ask in Side chat"
}}
onAddSelectionToConversation={vi.fn()}
onOpenForkSourceSession={onOpenForkSourceSession}
showTimelineSkeleton={false}
showUnavailableChatEmpty={false}
timelineContentRef={createRef()}
timelineRef={createRef()}
virtualScrollControllerRef={createRef()}
workspaceAppIcons={[]}
/>
);
fireEvent.click(
screen.getByRole("button", { name: "Continued from task" })
);
expect(onOpenForkSourceSession).toHaveBeenCalledWith("source-session");
expect(timelineRenderSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
turnAttachments: [
expect.objectContaining({
anchorTurnId: "target-turn",
id: "fork-lineage:fork-operation",
missingAnchorBehavior: "hide"
})
]
})
);
});
});