feat(webui): unify turn observability
This commit is contained in:
@@ -609,12 +609,33 @@ describe("ThreadShell", () => {
|
||||
),
|
||||
);
|
||||
|
||||
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",
|
||||
expect(await screen.findByTitle("fast · gpt-5.5 · OpenAI Codex")).toBeInTheDocument();
|
||||
expect(screen.queryByTitle("Default · deepseek-v4-pro · DeepSeek")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to the current preset while a renamed session reference is stale", async () => {
|
||||
const client = makeClient();
|
||||
const settings = settingsWithFastPreset();
|
||||
settings.agent.model_preset = "fast";
|
||||
settings.model_presets = settings.model_presets.map((preset) => ({
|
||||
...preset,
|
||||
active: preset.name === "fast",
|
||||
}));
|
||||
render(
|
||||
wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("renamed-preset", "old-fast")}
|
||||
title="Renamed preset"
|
||||
onToggleSidebar={() => {}}
|
||||
settingsSnapshot={settings}
|
||||
/>,
|
||||
"openai-codex/gpt-5.5",
|
||||
),
|
||||
);
|
||||
|
||||
expect(await screen.findByTitle("fast · gpt-5.5 · OpenAI Codex")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("switches through every named preset while preserving call-order priority", async () => {
|
||||
@@ -641,19 +662,18 @@ describe("ThreadShell", () => {
|
||||
));
|
||||
const { rerender } = render(view("default"));
|
||||
|
||||
const badge = await screen.findByRole("spinbutton", { name: "Default" });
|
||||
const badge = await screen.findByRole("button", { name: "Default" });
|
||||
expect(badge).toHaveTextContent("Default");
|
||||
fireEvent.keyDown(badge, { key: "ArrowDown" });
|
||||
fireEvent.click(badge);
|
||||
fireEvent.click(await screen.findByRole("option", { name: /^fast\b/i }));
|
||||
|
||||
expect(client.sendSystemCommand).toHaveBeenCalledWith(
|
||||
"preset-order",
|
||||
"/model fast",
|
||||
);
|
||||
expect(await screen.findByText("fast")).toBeInTheDocument();
|
||||
fireEvent.keyDown(
|
||||
screen.getByRole("spinbutton", { name: "fast" }),
|
||||
{ key: "End" },
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "fast" }));
|
||||
fireEvent.click(await screen.findByRole("option", { name: /^extra\b/i }));
|
||||
expect(client.sendSystemCommand).toHaveBeenLastCalledWith(
|
||||
"preset-order",
|
||||
"/model extra",
|
||||
@@ -696,15 +716,11 @@ describe("ThreadShell", () => {
|
||||
),
|
||||
);
|
||||
|
||||
const badge = await screen.findByLabelText("fast");
|
||||
fireEvent.focus(badge);
|
||||
expect(await screen.findByRole("tooltip")).toHaveTextContent(
|
||||
"fast · gpt-4 · Company Proxy",
|
||||
);
|
||||
expect(await screen.findByTitle("fast · gpt-4 · Company Proxy")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("only highlights fallback model updates without replacing the preset label", async () => {
|
||||
it("shows the effective fallback model in the composer badge", async () => {
|
||||
const client = makeClient();
|
||||
render(wrap(
|
||||
client,
|
||||
@@ -718,7 +734,8 @@ describe("ThreadShell", () => {
|
||||
));
|
||||
|
||||
expect(await screen.findByText("Default")).toBeInTheDocument();
|
||||
const configuredBadge = screen.getByTestId("composer-model-logo-openai_codex").parentElement;
|
||||
const configuredLogo = await screen.findByTestId("composer-model-logo-openai_codex");
|
||||
const configuredBadge = configuredLogo.parentElement;
|
||||
expect(configuredBadge).not.toBeNull();
|
||||
expect(configuredBadge).toHaveClass("composer-model-badge");
|
||||
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
||||
@@ -728,31 +745,34 @@ describe("ThreadShell", () => {
|
||||
event: "turn_model_updated",
|
||||
chat_id: "fallback-model",
|
||||
model_name: "openai-codex/gpt-5.5",
|
||||
model_preset: "Default",
|
||||
});
|
||||
});
|
||||
|
||||
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
||||
expect(screen.getByText("Default")).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
client._emitChat("fallback-model", {
|
||||
event: "turn_model_updated",
|
||||
chat_id: "fallback-model",
|
||||
model_name: "deepseek/deepseek-chat",
|
||||
fallback: true,
|
||||
});
|
||||
});
|
||||
|
||||
const logo = screen.getByTestId("composer-model-logo-openai_codex");
|
||||
const logo = await screen.findByTestId("composer-model-logo-deepseek");
|
||||
const badge = logo.parentElement;
|
||||
expect(badge).not.toBeNull();
|
||||
expect(badge).toBe(configuredBadge);
|
||||
expect(screen.getByText("Default")).toBeInTheDocument();
|
||||
expect(screen.queryByText("deepseek-chat")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Default")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("deepseek-chat")).toBeInTheDocument();
|
||||
expect(badge).toHaveAttribute("data-fallback", "true");
|
||||
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");
|
||||
expect(badge).toHaveAttribute(
|
||||
"title",
|
||||
"Default · using deepseek/deepseek-chat",
|
||||
);
|
||||
expect(logo).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
client._emitChat("fallback-model", {
|
||||
@@ -766,12 +786,7 @@ describe("ThreadShell", () => {
|
||||
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
|
||||
).not.toHaveAttribute("data-fallback");
|
||||
});
|
||||
expect(screen.getByRole("tooltip")).toHaveTextContent(
|
||||
"Default · gpt-5.5 · OpenAI Codex",
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
|
||||
).toBe(badge);
|
||||
expect(screen.getByText("Default")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens model settings from the unconfigured model badge", async () => {
|
||||
@@ -1084,10 +1099,8 @@ describe("ThreadShell", () => {
|
||||
));
|
||||
const { rerender } = render(view(null));
|
||||
|
||||
fireEvent.keyDown(
|
||||
await screen.findByRole("spinbutton", { name: "Default" }),
|
||||
{ key: "ArrowDown" },
|
||||
);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Default" }));
|
||||
fireEvent.click(await screen.findByRole("option", { name: /^fast\b/i }));
|
||||
expect(await screen.findByText("fast")).toBeInTheDocument();
|
||||
expect(client.sendSystemCommand).not.toHaveBeenCalled();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user