Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
763 lines
24 KiB
TypeScript
763 lines
24 KiB
TypeScript
import { fireEvent, render, screen } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { TooltipProvider } from "@tutti-os/ui-system";
|
|
import type { AgentQuickPromptLabels } from "./agentQuickPromptLabels";
|
|
import { AgentQuickPromptPopover } from "./AgentQuickPromptPopover";
|
|
import type { AgentQuickPromptLibraryController } from "./useAgentQuickPromptLibrary";
|
|
|
|
const labels = new Proxy(
|
|
{
|
|
deleteDescription: (title: string) => `Delete ${title}`,
|
|
dragCancel: (title: string) => `Canceled ${title}`,
|
|
dragDrop: (title: string) => `Dropped ${title}`,
|
|
dragHandle: (title: string) => `Reorder ${title}`,
|
|
dragMove: (title: string) => `Moving ${title}`,
|
|
dragStart: (title: string) => `Picked up ${title}`,
|
|
title: "Quick prompts",
|
|
trigger: "Prompts",
|
|
triggerTooltip: "Choose a quick prompt",
|
|
searchPlaceholder: "Search quick prompts",
|
|
startSorting: "Reorder",
|
|
finishSorting: "Done",
|
|
insertionError:
|
|
"Could not insert into the Composer. Try again from the quick prompt list.",
|
|
reorderDisabledMinimum:
|
|
"At least two quick prompts are required to adjust their order",
|
|
reorderDisabledPending:
|
|
"Wait for the current quick prompt change to finish",
|
|
reorderDisabledSearch: "Clear the search to adjust prompt order",
|
|
reorderDisabledUnsupported: "This host does not support prompt reordering",
|
|
add: "New prompt",
|
|
createFromTemplate: "Recommended templates",
|
|
moreActions: "More prompt actions",
|
|
edit: "Edit",
|
|
delete: "Delete",
|
|
empty: "No quick prompts yet",
|
|
loadError: "Quick prompts could not be loaded",
|
|
loading: "Loading quick prompts…",
|
|
noResults: "No matching quick prompts",
|
|
recommendedTemplatesTitle: "Recommended templates",
|
|
recommendedTemplatesDescription:
|
|
"Choose one to prefill the editor. Saving adds it as a quick prompt and inserts it into the Composer.",
|
|
returnToPrompts: "My prompts",
|
|
useTemplate: "Use template",
|
|
recommendedTemplates: [
|
|
{
|
|
id: "summary-common-prompts",
|
|
title: "Summarize common prompts",
|
|
description: "Find insights and repeated work patterns",
|
|
content: "Summarize my common prompts"
|
|
},
|
|
{
|
|
id: "understand-context",
|
|
title: "Understand the situation",
|
|
description: "Summarize context, constraints, risks, and next steps",
|
|
content: "Summarize the situation"
|
|
},
|
|
{
|
|
id: "create-action-plan",
|
|
title: "Create an action plan",
|
|
description: "Break a goal into prioritized, verifiable steps",
|
|
content: "Create an action plan"
|
|
},
|
|
{
|
|
id: "review-and-improve",
|
|
title: "Review and improve",
|
|
description: "Find gaps, risks, and practical improvements",
|
|
content: "Review and improve"
|
|
},
|
|
{
|
|
id: "draft-clear-update",
|
|
title: "Draft a clear update",
|
|
description: "Write a concise explanation for the intended audience",
|
|
content: "Draft an update"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
get: (target, property) => Reflect.get(target, property) ?? String(property)
|
|
}
|
|
) as unknown as AgentQuickPromptLabels;
|
|
|
|
function controller(
|
|
patch: Partial<AgentQuickPromptLibraryController> = {}
|
|
): AgentQuickPromptLibraryController {
|
|
const prompt = {
|
|
id: "prompt-1",
|
|
title: "Review",
|
|
content: "Review the current change",
|
|
version: 1,
|
|
createdAtUnixMs: 1,
|
|
updatedAtUnixMs: 2
|
|
};
|
|
return {
|
|
capabilityAvailable: true,
|
|
canReorder: false,
|
|
close: vi.fn(),
|
|
closeDialog: vi.fn(),
|
|
deletePrompt: vi.fn(),
|
|
filteredPrompts: [prompt],
|
|
isDeleting: false,
|
|
isEditorOpen: false,
|
|
isPopoverOpen: true,
|
|
isSaving: false,
|
|
isInteractionLocked: false,
|
|
isReordering: false,
|
|
initialDraft: null,
|
|
insertionError: false,
|
|
labels,
|
|
mode: "popover",
|
|
mutationError: null,
|
|
openCreate: vi.fn(),
|
|
openEdit: vi.fn(),
|
|
openPopover: vi.fn(),
|
|
promptToDelete: null,
|
|
retry: vi.fn(),
|
|
reorderCapabilityAvailable: true,
|
|
reorderError: null,
|
|
reorderPrompts: vi.fn(async () => true),
|
|
saveDraft: vi.fn(async () => true),
|
|
searchQuery: "",
|
|
showReorderHandles: false,
|
|
selectPrompt: vi.fn(),
|
|
selectedPrompt: null,
|
|
setPopoverOpen: vi.fn(),
|
|
setSearchQuery: vi.fn(),
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [prompt],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
},
|
|
submitDelete: vi.fn(async () => true),
|
|
...patch
|
|
};
|
|
}
|
|
|
|
describe("AgentQuickPromptPopover", () => {
|
|
it("uses the shared composer trigger color and icon sizing", () => {
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={controller()} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const trigger = screen.getByRole("button", {
|
|
name: "Choose a quick prompt"
|
|
});
|
|
expect(trigger).toHaveClass("agent-gui-node__composer-menu-trigger");
|
|
expect(trigger).toHaveClass("focus-visible:!outline-2");
|
|
expect(trigger.querySelector("svg")).toHaveClass("size-4");
|
|
});
|
|
|
|
it("keeps selection and direct icon management controls as sibling buttons", () => {
|
|
const subject = controller();
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
const selection = screen.getByRole("button", { name: /^Review/u });
|
|
expect(selection.querySelector("button")).toBeNull();
|
|
const edit = screen.getByRole("button", { name: "Edit" });
|
|
const remove = screen.getByRole("button", { name: "Delete" });
|
|
expect(edit).toBeInTheDocument();
|
|
expect(remove).toBeInTheDocument();
|
|
fireEvent.pointerDown(edit, { button: 0 });
|
|
expect(subject.openEdit).toHaveBeenCalledOnce();
|
|
fireEvent.pointerDown(remove, { button: 0 });
|
|
expect(subject.deletePrompt).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("shows reorder handles only in explicit sorting mode", () => {
|
|
const first = controller().filteredPrompts[0]!;
|
|
const second = {
|
|
...first,
|
|
id: "prompt-2",
|
|
title: "Plan",
|
|
content: "Plan the current change"
|
|
};
|
|
const subject = controller({
|
|
canReorder: true,
|
|
filteredPrompts: [first, second],
|
|
showReorderHandles: true,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [first, second],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
});
|
|
const rendered = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(screen.queryByRole("button", { name: "Reorder Review" })).toBeNull();
|
|
fireEvent.click(screen.getByRole("button", { name: "Reorder" }));
|
|
|
|
expect(
|
|
screen.getByRole("button", { name: "Reorder Review" })
|
|
).toBeEnabled();
|
|
expect(screen.queryByPlaceholderText("Search quick prompts")).toBeNull();
|
|
expect(screen.queryByRole("button", { name: "Edit" })).toBeNull();
|
|
expect(screen.queryByRole("button", { name: "Delete" })).toBeNull();
|
|
expect(
|
|
screen.queryByRole("button", {
|
|
name: /Summarize common prompts.*Use template/u
|
|
})
|
|
).toBeNull();
|
|
expect(
|
|
screen.queryByRole("button", {
|
|
name: "Recommended templates"
|
|
})
|
|
).toBeNull();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
canReorder: false,
|
|
filteredPrompts: [first, second],
|
|
isInteractionLocked: true,
|
|
showReorderHandles: true,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [first, second],
|
|
error: null,
|
|
revision: 2,
|
|
orderMutationPending: true,
|
|
pendingMutationIds: [first.id]
|
|
}
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole("button", { name: "Reorder Review" })
|
|
).toBeDisabled();
|
|
expect(screen.queryByRole("button", { name: /^Review/u })).toBeNull();
|
|
expect(screen.getByText("Review")).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "Done" })).toBeDisabled();
|
|
});
|
|
|
|
it("enters and finishes sorting on primary pointer down and keyboard click", () => {
|
|
const first = controller().filteredPrompts[0]!;
|
|
const second = { ...first, id: "prompt-2", title: "Plan" };
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
canReorder: true,
|
|
filteredPrompts: [first, second],
|
|
showReorderHandles: true,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [first, second],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const sort = screen.getByRole("button", { name: "Reorder" });
|
|
fireEvent.pointerDown(sort, { button: 0 });
|
|
expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument();
|
|
|
|
const done = screen.getByRole("button", { name: "Done" });
|
|
fireEvent.pointerDown(done, { button: 0 });
|
|
expect(screen.getByRole("button", { name: "Reorder" })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByPlaceholderText("Search quick prompts")
|
|
).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Reorder" }));
|
|
expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument();
|
|
expect(screen.queryByPlaceholderText("Search quick prompts")).toBeNull();
|
|
fireEvent.click(screen.getByRole("button", { name: "Done" }));
|
|
expect(screen.getByRole("button", { name: "Reorder" })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByPlaceholderText("Search quick prompts")
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("reports a Composer insertion failure without reporting a save failure", () => {
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({ insertionError: true })}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
"Could not insert into the Composer. Try again from the quick prompt list."
|
|
);
|
|
});
|
|
|
|
it("returns to the prompt list when insertion fails from the template view", () => {
|
|
const rendered = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={controller()} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Recommended templates" })
|
|
);
|
|
expect(
|
|
screen.getByRole("heading", { name: "Recommended templates", level: 2 })
|
|
).toBeInTheDocument();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({ insertionError: true })}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole("heading", { name: "Quick prompts", level: 2 })
|
|
).toBeInTheDocument();
|
|
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
"Could not insert into the Composer. Try again from the quick prompt list."
|
|
);
|
|
expect(
|
|
screen.getByRole("button", { name: /^Review/u })
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("explains why reordering is unavailable with stable reason priority", () => {
|
|
const first = controller().filteredPrompts[0]!;
|
|
const second = { ...first, id: "prompt-2", title: "Plan" };
|
|
const rendered = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={controller()} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(
|
|
screen.getByLabelText(
|
|
"At least two quick prompts are required to adjust their order"
|
|
)
|
|
).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "Reorder" })).toBeDisabled();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
filteredPrompts: [first, second],
|
|
reorderCapabilityAvailable: false,
|
|
showReorderHandles: false,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [first, second],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
expect(
|
|
screen.getByLabelText("This host does not support prompt reordering")
|
|
).toBeInTheDocument();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
filteredPrompts: [],
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "loading",
|
|
prompts: [],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
expect(screen.getByLabelText(labels.loading)).toBeInTheDocument();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
filteredPrompts: [],
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "error",
|
|
prompts: [],
|
|
error: "offline",
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
expect(screen.getByLabelText(labels.loadError)).toBeInTheDocument();
|
|
});
|
|
|
|
it("resets sorting mode when the controlled Popover closes externally", () => {
|
|
const first = controller().filteredPrompts[0]!;
|
|
const second = { ...first, id: "prompt-2", title: "Plan" };
|
|
const openController = controller({
|
|
canReorder: true,
|
|
filteredPrompts: [first, second],
|
|
showReorderHandles: true,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [first, second],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
});
|
|
const rendered = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={openController} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: "Reorder" }));
|
|
expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument();
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={{ ...openController, isPopoverOpen: false }}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={openController} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(screen.getByRole("button", { name: "Reorder" })).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Done" })).toBeNull();
|
|
expect(screen.queryByRole("button", { name: "Reorder Review" })).toBeNull();
|
|
});
|
|
|
|
it("disables recommended-template actions while a shared mutation is pending", () => {
|
|
const subject = controller({
|
|
filteredPrompts: [],
|
|
isInteractionLocked: true,
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: ["prompt-1"]
|
|
}
|
|
});
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const summaryTemplate = screen.getByRole("button", {
|
|
name: /Summarize common prompts.*Use template/u
|
|
});
|
|
const createTemplate = screen.getByRole("button", {
|
|
name: /Understand the situation.*Use template/u
|
|
});
|
|
expect(summaryTemplate).toBeDisabled();
|
|
expect(createTemplate).toBeDisabled();
|
|
fireEvent.pointerDown(summaryTemplate, { button: 0 });
|
|
fireEvent.click(summaryTemplate, { detail: 1 });
|
|
expect(subject.openCreate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("selects on primary pointer down and still accepts a later keyboard click", () => {
|
|
const subject = controller();
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const prompt = screen.getByRole("button", { name: /^Review/u });
|
|
fireEvent.pointerDown(prompt, { button: 0 });
|
|
expect(subject.selectPrompt).toHaveBeenCalledOnce();
|
|
fireEvent.click(prompt);
|
|
expect(subject.selectPrompt).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("keeps touch and pen actions cancellable until click", () => {
|
|
const subject = controller();
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const prompt = screen.getByRole("button", { name: /^Review/u });
|
|
fireEvent.pointerDown(prompt, { button: 0, pointerType: "touch" });
|
|
fireEvent.pointerCancel(prompt, { pointerType: "touch" });
|
|
expect(subject.selectPrompt).not.toHaveBeenCalled();
|
|
|
|
fireEvent.pointerDown(prompt, { button: 0, pointerType: "pen" });
|
|
expect(subject.selectPrompt).not.toHaveBeenCalled();
|
|
fireEvent.click(prompt, { detail: 1 });
|
|
expect(subject.selectPrompt).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("keeps the common-prompts recommendation inside the template view", () => {
|
|
const subject = controller();
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
expect(
|
|
screen.queryByRole("button", {
|
|
name: /Summarize common prompts.*Use template/u
|
|
})
|
|
).toBeNull();
|
|
fireEvent.pointerDown(
|
|
screen.getByRole("button", { name: "Recommended templates" }),
|
|
{ button: 0 }
|
|
);
|
|
const summaryTemplate = screen.getByRole("button", {
|
|
name: /Summarize common prompts.*Use template/u
|
|
});
|
|
fireEvent.pointerDown(summaryTemplate, { button: 0 });
|
|
fireEvent.click(summaryTemplate, { detail: 1 });
|
|
expect(subject.openCreate).toHaveBeenCalledWith(
|
|
{
|
|
title: "Summarize common prompts",
|
|
content: "Summarize my common prompts"
|
|
},
|
|
{
|
|
insertIntoComposerAfterSave: true,
|
|
usagePromptType: "recommended_template"
|
|
}
|
|
);
|
|
expect(subject.openCreate).toHaveBeenCalledOnce();
|
|
expect(subject.saveDraft).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("starts creation on primary pointer down so the Dialog survives Popover dismissal", () => {
|
|
const subject = controller();
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
fireEvent.pointerDown(screen.getByRole("button", { name: "New prompt" }), {
|
|
button: 0
|
|
});
|
|
expect(subject.openCreate).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("prefills the create editor for every recommended template", () => {
|
|
const subject = controller({
|
|
filteredPrompts: [],
|
|
snapshot: {
|
|
enabled: true,
|
|
status: "ready",
|
|
prompts: [],
|
|
error: null,
|
|
revision: 1,
|
|
pendingMutationIds: []
|
|
}
|
|
});
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={subject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
const summaryTemplate = screen.getByRole("button", {
|
|
name: /Summarize common prompts.*Use template/u
|
|
});
|
|
fireEvent.click(summaryTemplate);
|
|
expect(subject.openCreate).toHaveBeenNthCalledWith(
|
|
1,
|
|
{
|
|
title: "Summarize common prompts",
|
|
content: "Summarize my common prompts"
|
|
},
|
|
{
|
|
insertIntoComposerAfterSave: true,
|
|
usagePromptType: "recommended_template"
|
|
}
|
|
);
|
|
|
|
const createTemplate = screen.getByRole("button", {
|
|
name: /Understand the situation.*Use template/u
|
|
});
|
|
fireEvent.click(createTemplate);
|
|
expect(subject.openCreate).toHaveBeenNthCalledWith(
|
|
2,
|
|
{
|
|
title: "Understand the situation",
|
|
content: "Summarize the situation"
|
|
},
|
|
{
|
|
insertIntoComposerAfterSave: true,
|
|
usagePromptType: "recommended_template"
|
|
}
|
|
);
|
|
expect(subject.openCreate).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("lets a non-empty library reopen recommended templates in the same Popover", () => {
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={controller()} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
|
|
fireEvent.pointerDown(
|
|
screen.getByRole("button", {
|
|
name: "Recommended templates"
|
|
}),
|
|
{ button: 0 }
|
|
);
|
|
expect(
|
|
screen.getByRole("heading", { name: "Recommended templates", level: 2 })
|
|
).toBeInTheDocument();
|
|
expect(screen.queryByPlaceholderText("Search quick prompts")).toBeNull();
|
|
expect(
|
|
screen.getByRole("button", { name: "My prompts" })
|
|
).toBeInTheDocument();
|
|
|
|
fireEvent.pointerDown(screen.getByRole("button", { name: "My prompts" }), {
|
|
button: 0
|
|
});
|
|
expect(
|
|
screen.getByRole("heading", { name: "Quick prompts", level: 2 })
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByPlaceholderText("Search quick prompts")
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("runs dialog cancellation and deletion on primary pointer down", () => {
|
|
const createSubject = controller({
|
|
isEditorOpen: true,
|
|
isPopoverOpen: false,
|
|
mode: "create"
|
|
});
|
|
const createRender = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={createSubject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
fireEvent.pointerDown(screen.getByRole("button", { name: labels.cancel }), {
|
|
button: 0
|
|
});
|
|
expect(createSubject.closeDialog).toHaveBeenCalledOnce();
|
|
createRender.unmount();
|
|
|
|
const deleteSubject = controller({
|
|
isPopoverOpen: false,
|
|
mode: "delete",
|
|
promptToDelete: controller().filteredPrompts[0]!
|
|
});
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover controller={deleteSubject} disabled={false} />
|
|
</TooltipProvider>
|
|
);
|
|
fireEvent.pointerDown(
|
|
screen.getByRole("button", { name: labels.deleteConfirm }),
|
|
{ button: 0 }
|
|
);
|
|
expect(deleteSubject.submitDelete).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("preserves an edited draft when a conflict refreshes the same prompt", () => {
|
|
const prompt = controller().filteredPrompts[0]!;
|
|
const rendered = render(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
isEditorOpen: true,
|
|
isPopoverOpen: false,
|
|
mode: "edit",
|
|
selectedPrompt: prompt
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
const content = screen.getByLabelText(labels.contentLabel);
|
|
fireEvent.change(content, { target: { value: "My unsaved draft" } });
|
|
|
|
rendered.rerender(
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
isEditorOpen: true,
|
|
isPopoverOpen: false,
|
|
mode: "edit",
|
|
selectedPrompt: { ...prompt, version: 2, updatedAtUnixMs: 5 }
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
);
|
|
expect(screen.getByLabelText(labels.contentLabel)).toHaveValue(
|
|
"My unsaved draft"
|
|
);
|
|
});
|
|
|
|
it("keeps Enter in the editor Dialog out of the Composer shortcut", () => {
|
|
const onComposerKeyDown = vi.fn();
|
|
render(
|
|
<div onKeyDown={onComposerKeyDown}>
|
|
<TooltipProvider>
|
|
<AgentQuickPromptPopover
|
|
controller={controller({
|
|
isEditorOpen: true,
|
|
isPopoverOpen: false,
|
|
mode: "create"
|
|
})}
|
|
disabled={false}
|
|
/>
|
|
</TooltipProvider>
|
|
</div>
|
|
);
|
|
|
|
fireEvent.keyDown(screen.getByLabelText(labels.titleLabel), {
|
|
key: "Enter"
|
|
});
|
|
expect(onComposerKeyDown).not.toHaveBeenCalled();
|
|
});
|
|
});
|