feat(webui): polish model preset interaction

This commit is contained in:
chengyongru
2026-08-21 16:49:30 +08:00
committed by chengyongru
parent 953d2ed217
commit d28687e2e1
4 changed files with 148 additions and 20 deletions
+49 -1
View File
@@ -571,6 +571,53 @@ describe("ThreadComposer", () => {
expect(screen.queryByText(/Enter to send/)).not.toBeInTheDocument();
});
it("shows model details in the shared tooltip without a native title", async () => {
render(
<ThreadComposer
onSend={vi.fn()}
modelLabel="gpt-4o"
modelDetail="gpt-4o"
modelProvider="openai"
modelProviderLabel="OpenAI"
placeholder="Type your message..."
/>,
);
const badge = screen.getByLabelText("gpt-4o");
expect(badge).not.toHaveAttribute("title");
fireEvent.focus(badge);
expect(await screen.findByRole("tooltip")).toHaveTextContent("gpt-4o · OpenAI");
});
it("smoothly cycles to the next preset on click", () => {
vi.useFakeTimers();
let runFrame: FrameRequestCallback | null = null;
vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
runFrame = callback;
return 1;
});
vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => undefined);
const { badge, onPresetChange } = renderPresetComposer();
fireEvent.click(badge);
expect(badge).toHaveAttribute("data-switching", "true");
const track = screen.getByTestId("composer-model-pill-track");
expect(track).not.toHaveAttribute("data-settling");
expect(track).toHaveStyle({ transform: "translate3d(0, -40px, 0)" });
act(() => runFrame?.(16));
expect(onPresetChange).toHaveBeenCalledWith("dflash");
expect(badge).toHaveAttribute("data-settling", "true");
expect(track).toHaveAttribute("data-settling", "true");
expect(track).toHaveStyle({ transform: "translate3d(0, -80px, 0)" });
act(() => vi.advanceTimersByTime(260));
expect(badge).not.toHaveAttribute("data-switching");
});
it("scrolls complete preset pills after a left-button long press and wraps", () => {
vi.useFakeTimers();
const { badge, onPresetChange } = renderPresetComposer();
@@ -582,7 +629,6 @@ describe("ThreadComposer", () => {
});
badge.dispatchEvent(idleTouchMove);
expect(idleTouchMove.defaultPrevented).toBe(false);
fireEvent.click(badge);
pointerDown(badge);
fireEvent.pointerMove(badge, { clientY: 80, pointerId: 7, pointerType: "mouse" });
act(() => vi.advanceTimersByTime(500));
@@ -639,6 +685,8 @@ describe("ThreadComposer", () => {
});
expect(onPresetChange).toHaveBeenCalledWith("dspro");
fireEvent.click(badge);
expect(onPresetChange).toHaveBeenCalledTimes(1);
expect(badge).toHaveAttribute("data-settling", "true");
expect(track).toHaveAttribute("data-settling", "true");
act(() => {
+18 -10
View File
@@ -609,8 +609,12 @@ describe("ThreadShell", () => {
),
);
expect(await screen.findByTitle("fast · gpt-5.5 · OpenAI Codex")).toBeInTheDocument();
expect(screen.queryByTitle("Default · deepseek-v4-pro · DeepSeek")).not.toBeInTheDocument();
const badge = await screen.findByLabelText("fast");
expect(badge).not.toHaveAttribute("title");
fireEvent.focus(badge);
expect(await screen.findByRole("tooltip")).toHaveTextContent(
"fast · gpt-5.5 · OpenAI Codex",
);
});
it("switches through every named preset while preserving call-order priority", async () => {
@@ -692,7 +696,11 @@ describe("ThreadShell", () => {
),
);
expect(await screen.findByTitle("fast · gpt-4 · Company Proxy")).toBeInTheDocument();
const badge = await screen.findByLabelText("fast");
fireEvent.focus(badge);
expect(await screen.findByRole("tooltip")).toHaveTextContent(
"fast · gpt-4 · Company Proxy",
);
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
});
@@ -740,11 +748,11 @@ describe("ThreadShell", () => {
expect(screen.getByText("Default")).toBeInTheDocument();
expect(screen.queryByText("deepseek-chat")).not.toBeInTheDocument();
expect(badge).toHaveAttribute("data-fallback", "true");
expect(badge).toHaveAttribute(
"title",
"deepseek/deepseek-chat",
);
expect(badge).not.toHaveAttribute("title");
expect(logo).not.toHaveAttribute("data-fallback");
const trigger = screen.getByLabelText("Default");
fireEvent.focus(trigger);
expect(await screen.findByRole("tooltip")).toHaveTextContent("deepseek/deepseek-chat");
act(() => {
client._emitChat("fallback-model", {
@@ -758,9 +766,9 @@ describe("ThreadShell", () => {
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
).not.toHaveAttribute("data-fallback");
});
expect(
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
).toHaveAttribute("title", "Default · gpt-5.5 · OpenAI Codex");
expect(screen.getByRole("tooltip")).toHaveTextContent(
"Default · gpt-5.5 · OpenAI Codex",
);
expect(
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
).toBe(badge);