feat(cli): historique de messages navigable haut/bas dans la CLI custom + contrôles de cellule visibles pendant busy — persistance localStorage du transcript custom (CustomAgentChatView), layering des contrôles sous l'overlay busy (LayoutGrid), bump pointeur IdeaSDK (docs plugin minimal + hot reload) (QA verte: 65 tests CustomAgentChatView, 25 tests LayoutGrid chat/layering, npm build frontend OK)
This commit is contained in:
@ -140,6 +140,7 @@ function setMeasuredTextOverflow(
|
||||
describe("CustomAgentChatView", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
window.localStorage.clear();
|
||||
__clearCustomAgentChatViewCacheForTests();
|
||||
__resetProjectCliInteractionActivityForTests();
|
||||
});
|
||||
@ -236,6 +237,144 @@ describe("CustomAgentChatView", () => {
|
||||
expect(screen.queryByRole("button", { name: "Ouvrir en TUI native" })).toBeNull();
|
||||
});
|
||||
|
||||
it("recalls previously sent prompts with arrow up and down", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(
|
||||
async (_sessionId: string, prompt: string, onChunk: (chunk: ReplyChunk) => void) => {
|
||||
onChunk({ kind: "final", content: `answer ${prompt}` });
|
||||
},
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView
|
||||
projectId="project-1"
|
||||
agentId="agent-1"
|
||||
agentName="Worker"
|
||||
profile={profile}
|
||||
cwd="/repo"
|
||||
layoutId="layout-1"
|
||||
nodeId="node-1"
|
||||
sessionId="chat-session-1"
|
||||
conversationId="conversation-1"
|
||||
onSessionId={vi.fn()}
|
||||
onConversationId={vi.fn()}
|
||||
/>
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
const composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
await waitFor(() => expect(agent.reattachAgentChat).toHaveBeenCalled());
|
||||
|
||||
fireEvent.change(composer, { target: { value: "first prompt" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
"first prompt",
|
||||
expect.any(Function),
|
||||
));
|
||||
|
||||
fireEvent.change(composer, { target: { value: "second prompt" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
"second prompt",
|
||||
expect.any(Function),
|
||||
));
|
||||
|
||||
fireEvent.keyDown(composer, { key: "ArrowUp" });
|
||||
await waitFor(() => expect(composer.value).toBe("second prompt"));
|
||||
fireEvent.keyDown(composer, { key: "ArrowUp" });
|
||||
await waitFor(() => expect(composer.value).toBe("first prompt"));
|
||||
fireEvent.keyDown(composer, { key: "ArrowDown" });
|
||||
await waitFor(() => expect(composer.value).toBe("second prompt"));
|
||||
|
||||
fireEvent.change(composer, { target: { value: "draft in progress" } });
|
||||
fireEvent.keyDown(composer, { key: "ArrowUp" });
|
||||
await waitFor(() => expect(composer.value).toBe("second prompt"));
|
||||
fireEvent.keyDown(composer, { key: "ArrowDown" });
|
||||
await waitFor(() => expect(composer.value).toBe("draft in progress"));
|
||||
});
|
||||
|
||||
it("persists prompt history across a custom chat remount", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
reattachAgentChat: vi.fn(async (sessionId: string) => ({
|
||||
sessionId,
|
||||
scrollback: [],
|
||||
})),
|
||||
sendAgentChat: vi.fn(
|
||||
async (_sessionId: string, prompt: string, onChunk: (chunk: ReplyChunk) => void) => {
|
||||
onChunk({ kind: "final", content: `answer ${prompt}` });
|
||||
},
|
||||
),
|
||||
cancelAgentChat: vi.fn(async () => {}),
|
||||
closeAgentChat: vi.fn(async () => {}),
|
||||
};
|
||||
const props = {
|
||||
projectId: "project-1",
|
||||
agentId: "agent-1",
|
||||
agentName: "Worker",
|
||||
profile,
|
||||
cwd: "/repo",
|
||||
layoutId: "layout-1",
|
||||
nodeId: "node-1",
|
||||
sessionId: "chat-session-1",
|
||||
conversationId: "conversation-1",
|
||||
onSessionId: vi.fn(),
|
||||
onConversationId: vi.fn(),
|
||||
};
|
||||
|
||||
const view = render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView {...props} />
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
let composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
fireEvent.change(composer, { target: { value: "remember me" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));
|
||||
await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledWith(
|
||||
"chat-session-1",
|
||||
"remember me",
|
||||
expect.any(Function),
|
||||
));
|
||||
|
||||
view.unmount();
|
||||
render(
|
||||
<DIProvider
|
||||
gateways={{
|
||||
agent,
|
||||
system: { pickFile: vi.fn(async () => null) },
|
||||
} as unknown as Gateways}
|
||||
>
|
||||
<CustomAgentChatView {...props} />
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
composer = await screen.findByLabelText(/message CLI custom/) as HTMLTextAreaElement;
|
||||
fireEvent.keyDown(composer, { key: "ArrowUp" });
|
||||
await waitFor(() => expect(composer.value).toBe("remember me"));
|
||||
});
|
||||
|
||||
it("repaints attachment labels from user prompt scrollback", async () => {
|
||||
const agent = {
|
||||
launchAgentChat: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user