feat(quick-pane): polish compact chat controls

This commit is contained in:
2026-08-27 17:18:16 +02:00
parent 60fb91f375
commit 880cddf3cf
11 changed files with 317 additions and 40 deletions
+91 -2
View File
@@ -20,6 +20,7 @@ const mocks = vi.hoisted(() => ({
invoke: vi.fn(),
listen: vi.fn(),
onFocusChanged: vi.fn(),
startDragging: vi.fn(),
focusHandler: undefined as
| ((event: { payload: boolean }) => void)
| undefined,
@@ -28,7 +29,10 @@ const mocks = vi.hoisted(() => ({
vi.mock("@tauri-apps/api/core", () => ({ invoke: mocks.invoke }));
vi.mock("@tauri-apps/api/event", () => ({ listen: mocks.listen }));
vi.mock("@tauri-apps/api/window", () => ({
getCurrentWindow: () => ({ onFocusChanged: mocks.onFocusChanged }),
getCurrentWindow: () => ({
onFocusChanged: mocks.onFocusChanged,
startDragging: mocks.startDragging,
}),
}));
import QuickPane from "./QuickPane";
@@ -212,6 +216,7 @@ describe("QuickPane", () => {
vi.restoreAllMocks();
vi.clearAllMocks();
mocks.focusHandler = undefined;
mocks.startDragging.mockResolvedValue(undefined);
mocks.listen.mockResolvedValue(vi.fn());
mocks.onFocusChanged.mockImplementation((handler) => {
mocks.focusHandler = handler;
@@ -227,12 +232,95 @@ describe("QuickPane", () => {
expect(
Array.from(pane?.children ?? []).map((element) => element.className),
).toEqual([
"quick-pane-drag-strip",
"quick-pane-toolbar",
"quick-pane-composer",
"quick-pane-exchange",
]);
});
it("starts native dragging only from the strip above toolbar controls", async () => {
bridgeMock([defaultModels]);
const { container } = render(<QuickPane />);
await preparedTrigger();
fireEvent.pointerDown(container.querySelector(".quick-pane-drag-strip")!);
expect(mocks.startDragging).toHaveBeenCalledTimes(1);
fireEvent.pointerDown(screen.getByRole("button", { name: "Close Quick Pane" }));
expect(mocks.startDragging).toHaveBeenCalledTimes(1);
});
it("derives thinking choices from selected model metadata and falls back to Off", async () => {
const thinkingModels: Model[] = [
{
provider: "provider",
id: "quick/model",
name: "Quick Model",
thinkingLevelMap: { off: "off", high: "high" },
},
];
bridgeMock([thinkingModels], () => runtimeSnapshot(thinkingModels, "", true));
render(<QuickPane />);
await preparedTrigger();
const thinking = screen.getByRole("combobox", { name: "Thinking" });
expect(within(thinking).getAllByRole("option").map((option) => option.textContent)).toEqual([
"Off",
"High",
]);
await userEvent.selectOptions(thinking, "high");
await ask("Inspect");
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("set_thinking_level", {
agentId: "agent-1",
level: "high",
}),
);
});
it("keeps Abort active-only and Renew idle after an exchange", async () => {
let streaming = true;
const tracker = bridgeMock([defaultModels, defaultModels], () =>
runtimeSnapshot(defaultModels, "Partial answer", streaming),
);
render(<QuickPane />);
await preparedTrigger();
expect(screen.queryByRole("button", { name: "Abort" })).toBeNull();
expect(screen.queryByRole("button", { name: "Renew" })).toBeNull();
await ask("Inspect");
expect(await screen.findByRole("button", { name: "Abort" })).toBeEnabled();
await userEvent.click(screen.getByRole("button", { name: "Abort" }));
await waitFor(() => expect(tracker.runtimeCount()).toBe(2));
expect(mocks.invoke).toHaveBeenCalledWith("abort", { agentId: "agent-1" });
await waitFor(() =>
expect(screen.getByRole("button", { name: "Send request" })).toBeEnabled(),
);
streaming = false;
await ask("Again");
expect(await screen.findByRole("button", { name: "Renew" })).toBeEnabled();
});
it("reports Abort failures without replacing active chat", async () => {
bridgeMock([defaultModels], () =>
runtimeSnapshot(defaultModels, "Partial answer", true),
);
const baseImplementation = mocks.invoke.getMockImplementation();
mocks.invoke.mockImplementation((command: string, payload?: Record<string, unknown>) => {
if (command === "abort") return Promise.reject(new Error("abort unavailable"));
return baseImplementation?.(command, payload);
});
render(<QuickPane />);
await preparedTrigger();
await ask("Inspect");
await userEvent.click(await screen.findByRole("button", { name: "Abort" }));
expect(await screen.findByRole("alert")).toHaveTextContent(
"Could not abort quick request: Error: abort unavailable",
);
expect(mocks.invoke).not.toHaveBeenCalledWith("close_quick_runtime", {
runtimeId: "runtime-1",
});
expect(screen.getByRole("button", { name: "Abort" })).toBeEnabled();
});
it("keeps downward model menu inside the scroll-contained pane", async () => {
const user = userEvent.setup();
bridgeMock([defaultModels]);
@@ -371,6 +459,7 @@ describe("QuickPane", () => {
const continueButton = await screen.findByRole("button", {
name: "Continue in full session",
});
expect(continueButton.closest(".quick-pane-assistant-footer")).not.toBeNull();
expect(mocks.invoke).not.toHaveBeenCalledWith(
"create_session_runtime",
expect.anything(),
@@ -448,7 +537,7 @@ describe("QuickPane", () => {
"YouFirst request",
"AssistantFirst answer",
"YouSecond request",
"AssistantSecond answer",
"AssistantSecond answerContinue in full session",
]);
});