test(tui): covered welcome model synchronization

This commit is contained in:
can1357
2026-08-16 01:53:08 +02:00
parent 7e4fd9a686
commit a59fc754b6
2 changed files with 27 additions and 40 deletions
@@ -142,6 +142,33 @@ describe("InteractiveMode plan.defaultOnStartup", () => {
expect(session?.getActiveToolNames()).toContain("read");
});
it("keeps the welcome banner synchronized across startup and later model switches", async () => {
Settings.instance.set("startup.quiet", false);
const settings = Settings.isolated({ "plan.defaultOnStartup": true, "compaction.enabled": false });
settings.setModelRole("plan", "anthropic/claude-haiku-4-5:high");
const created = createHarness(settings);
const initialModel = session?.model;
if (!initialModel) throw new Error("Expected initial model");
await created.init({ suppressWelcomeIntro: true });
const planModel = session?.model;
if (!planModel) throw new Error("Expected plan model");
expect(planModel.id).toBe("claude-haiku-4-5");
const rendered = Bun.stripANSI(created.ui.render(120).join("\n"));
expect(rendered).toContain(planModel.name);
expect(rendered).not.toContain(initialModel.name);
const requestRenderSpy = vi.spyOn(created.ui, "requestRender");
requestRenderSpy.mockClear();
await session!.setModel(initialModel);
expect(requestRenderSpy).toHaveBeenCalled();
const switched = Bun.stripANSI(created.ui.render(120).join("\n"));
expect(switched).toContain(initialModel.name);
expect(switched).not.toContain(planModel.name);
});
it("activates write when entering plan mode even if it was hidden by discoveryMode (issue #3165)", async () => {
// `plan-mode-active.md` instructs the agent to draft the plan file with
// `write` and refine it with `edit`. Under `tools.discoveryMode === "all"`
@@ -62,43 +62,3 @@ describe("WelcomeComponent tips", () => {
expect(pickWeightedTip([], 0.5)).toBe("");
});
});
// Regression coverage for the stale welcome banner bug: the component is
// constructed with the session model at init time, but init-time model changes
// (plan.defaultOnStartup, #reconcileModeFromSession, delayed config load) can
// swap the model before the model_changed subscription exists. The catch-up
// resync in InteractiveMode.init() calls setModel() after those steps; these
// tests verify setModel() produces the correct rendered output.
describe("WelcomeComponent model name", () => {
beforeAll(async () => {
await Settings.init({ inMemory: true });
await initTheme(false);
});
it("updates the rendered model name after setModel()", () => {
const welcome = new WelcomeComponent("1.0.0", "DeepSeek V4 Pro", "deepseek");
const before = welcome.render(100).join("\n");
expect(before).toContain("DeepSeek V4 Pro");
welcome.setModel("GLM-5.2", "zhipu-coding-plan");
const after = welcome.render(100).join("\n");
expect(after).toContain("GLM-5.2");
expect(after).not.toContain("DeepSeek V4 Pro");
});
it("resyncs to the live model after init-time model switches (catch-up path)", () => {
// Banner constructed with the pre-config fallback model (the startup race
// picks an alphabetically-first provider before modelRoles.default loads).
const welcome = new WelcomeComponent("1.0.0", "DeepSeek V4 Pro", "deepseek");
expect(welcome.render(100).join("\n")).toContain("DeepSeek V4 Pro");
// Config loads, session switches to the real default — the model_changed
// event fires before the subscription exists, so InteractiveMode.init()
// calls setModel() explicitly as a catch-up resync.
welcome.setModel("GLM-5.2", "zhipu-coding-plan");
const rendered = welcome.render(100).join("\n");
expect(rendered).toContain("GLM-5.2");
expect(rendered).toContain("zhipu-coding-plan");
expect(rendered).not.toContain("DeepSeek");
});
});