Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/controller/useComposerModelChoiceHistory.spec.tsx
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

207 lines
6.1 KiB
TypeScript

import { act, renderHook, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it } from "vitest";
import type { AgentGUIComposerModelChoiceHistoryVM } from "../model/agentGuiNodeTypes";
import { useComposerModelChoiceHistory } from "./useComposerModelChoiceHistory";
const RECENTS_PREFIX = "agent-gui:composer-model-recents:";
const FAVORITES_PREFIX = "agent-gui:composer-model-favorites:";
beforeEach(() => {
globalThis.localStorage.clear();
});
describe("useComposerModelChoiceHistory", () => {
it("reconciles recents against an authoritative target catalog", async () => {
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}agent:ccs`,
'["old-model","valid-model"]'
);
globalThis.localStorage.setItem(
`${FAVORITES_PREFIX}agent:ccs`,
'["old-model"]'
);
const { result } = renderHook(() =>
useComposerModelChoiceHistory(
history("agent:ccs", catalog(["valid-model", "other-model"]))
)
);
await waitFor(() => {
expect(result.current.recentModelIds).toEqual(["valid-model"]);
});
act(() => result.current.refreshFromStorage());
expect(globalThis.localStorage.getItem(`${RECENTS_PREFIX}agent:ccs`)).toBe(
'["valid-model"]'
);
expect(
globalThis.localStorage.getItem(`${FAVORITES_PREFIX}agent:ccs`)
).toBe('["old-model"]');
});
it("removes an empty recents key after every remembered model is rejected", async () => {
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}agent:ccs`,
'["old-model"]'
);
const { result } = renderHook(() =>
useComposerModelChoiceHistory(
history("agent:ccs", catalog(["valid-model", "other-model"]))
)
);
act(() => result.current.refreshFromStorage());
await waitFor(() => {
expect(
globalThis.localStorage.getItem(`${RECENTS_PREFIX}agent:ccs`)
).toBeNull();
});
});
it.each([
["non-authoritative", catalog(["valid-model"], { authoritative: false })],
["loading", catalog(["valid-model"], { loading: true })],
["empty", catalog([])],
[
"requested-only",
catalog([], {
models: [{ value: "old-model", requested: true }]
})
],
[
"selected-only echo",
catalog([], {
effectiveModel: "old-model",
models: [{ value: "old-model" }]
})
]
])("preserves recents while the catalog is %s", async (_label, testimony) => {
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}agent:ccs`,
'["old-model"]'
);
const { result } = renderHook(() =>
useComposerModelChoiceHistory(history("agent:ccs", testimony))
);
await waitFor(() => {
expect(result.current.recentModelIds).toEqual(["old-model"]);
});
act(() => result.current.refreshFromStorage());
expect(globalThis.localStorage.getItem(`${RECENTS_PREFIX}agent:ccs`)).toBe(
'["old-model"]'
);
});
it("lazily migrates the legacy shared bucket through current catalog testimony", async () => {
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}default`,
'["old-model","valid-model"]'
);
globalThis.localStorage.setItem(
`${FAVORITES_PREFIX}default`,
'["favorite-model"]'
);
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}agent:ccs`,
'["target-model"]'
);
const { result } = renderHook(() =>
useComposerModelChoiceHistory(
history(
"agent:ccs",
catalog(["target-model", "valid-model", "other-model"])
)
)
);
act(() => result.current.refreshFromStorage());
await waitFor(() => {
expect(result.current.recentModelIds).toEqual([
"target-model",
"valid-model"
]);
expect(result.current.favoriteModelIds).toEqual(["favorite-model"]);
});
expect(
globalThis.localStorage.getItem(`${RECENTS_PREFIX}default`)
).toBeNull();
expect(
globalThis.localStorage.getItem(`${FAVORITES_PREFIX}default`)
).toBeNull();
});
it("fails closed without target identity and keeps targets isolated", () => {
globalThis.localStorage.setItem(
`${RECENTS_PREFIX}default`,
'["legacy-model"]'
);
const { result, rerender } = renderHook(
({ targetId }: { targetId: string | null }) =>
useComposerModelChoiceHistory(history(targetId, null)),
{ initialProps: { targetId: null as string | null } }
);
act(() => {
result.current.recordRecentModel("new-model");
result.current.toggleFavoriteModel("new-model");
});
expect(result.current.enabled).toBe(false);
expect(result.current.recentModelIds).toEqual([]);
expect(globalThis.localStorage.getItem(`${RECENTS_PREFIX}default`)).toBe(
'["legacy-model"]'
);
expect(
globalThis.localStorage.getItem(`${FAVORITES_PREFIX}default`)
).toBeNull();
rerender({ targetId: "agent:first" });
expect(result.current.enabled).toBe(true);
act(() => result.current.recordRecentModel("first-model"));
expect(
globalThis.localStorage.getItem(`${RECENTS_PREFIX}agent:first`)
).toBe('["first-model"]');
rerender({ targetId: "agent:second" });
expect(result.current.recentModelIds).toEqual([]);
});
it("refreshes from another window when the menu opens", () => {
const { result } = renderHook(() =>
useComposerModelChoiceHistory(history("agent:ccs", null))
);
const key = `${RECENTS_PREFIX}agent:ccs`;
globalThis.localStorage.setItem(key, '["external-model"]');
act(() => result.current.refreshFromStorage());
expect(result.current.recentModelIds).toEqual(["external-model"]);
});
});
function history(
targetId: string | null,
testimony: AgentGUIComposerModelChoiceHistoryVM["catalog"]
): AgentGUIComposerModelChoiceHistoryVM {
return { targetId, catalog: testimony };
}
function catalog(
modelIds: readonly string[],
overrides: Partial<
NonNullable<AgentGUIComposerModelChoiceHistoryVM["catalog"]>
> = {}
): NonNullable<AgentGUIComposerModelChoiceHistoryVM["catalog"]> {
return {
authoritative: true,
effectiveModel: null,
loading: false,
models: modelIds.map((value) => ({ value })),
...overrides
};
}