MagicCode/packages/ui/src/store.test.ts

115 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-10-11 22:41:17 +00:00
import { describe, expect, it } from "vitest";
import { UIStore } from "./store";
const kinds = (s: UIStore) => s.getSnapshot().items.map((i) => `${i.kind}:${i.text}`);
describe("UIStore", () => {
it("le texte streamé devient un message à la fin de la réponse", () => {
const s = new UIStore();
s.appendStreaming("Bon");
s.appendStreaming("jour\n");
expect(s.getSnapshot().streaming).toBe("Bonjour\n");
s.endStreaming();
expect(s.getSnapshot().streaming).toBe("");
expect(kinds(s)).toEqual(["assistant:Bonjour"]);
});
it("notifie les abonnés et fournit un instantané stable entre deux changements", () => {
const s = new UIStore();
let calls = 0;
const off = s.subscribe(() => calls++);
const before = s.getSnapshot();
expect(s.getSnapshot()).toBe(before);
s.log("info", "a");
expect(calls).toBe(1);
expect(s.getSnapshot()).not.toBe(before);
off();
s.log("info", "b");
expect(calls).toBe(1);
});
it("readInput : la saisie est journalisée, annulée = null, l'historique est transmis", async () => {
const s = new UIStore();
const first = s.readInput();
const p1 = s.getSnapshot().prompt;
expect(p1?.kind).toBe("input");
(p1 as { resolve(v: string | null): void }).resolve("bonjour");
expect(await first).toBe("bonjour");
expect(s.getSnapshot().prompt).toBeNull();
expect(kinds(s)).toEqual(["user:bonjour"]);
const second = s.readInput();
const p2 = s.getSnapshot().prompt as { history: string[]; resolve(v: string | null): void };
expect(p2.history).toEqual(["bonjour"]);
p2.resolve(null);
expect(await second).toBeNull();
expect(kinds(s)).toEqual(["user:bonjour"]); // rien de journalisé à l'annulation
});
it("select : présélection, valeur renvoyée, choix journalisé", async () => {
const s = new UIStore();
const choices = [
{ value: "a", label: "Alpha" },
{ value: "b", label: "Bravo" },
];
const result = s.select("Modèle", choices, "b");
const prompt = s.getSnapshot().prompt as { kind: string; initialIndex: number; resolve(v: unknown): void };
expect(prompt.kind).toBe("select");
expect(prompt.initialIndex).toBe(1);
prompt.resolve("a");
expect(await result).toBe("a");
expect(kinds(s)).toEqual(["choice:Modèle : Alpha"]);
});
it("select : annulation = null, liste vide = null sans invite", async () => {
const s = new UIStore();
const cancelled = s.select("Modèle", [{ value: 1, label: "un" }]);
(s.getSnapshot().prompt as { resolve(v: unknown): void }).resolve(null);
expect(await cancelled).toBeNull();
expect(await s.select("Vide", [])).toBeNull();
expect(s.getSnapshot().prompt).toBeNull();
});
it("confirm", async () => {
const s = new UIStore();
const answer = s.confirm("Autoriser Bash ?");
(s.getSnapshot().prompt as { resolve(v: boolean): void }).resolve(false);
expect(await answer).toBe(false);
expect(s.getSnapshot().prompt).toBeNull();
});
it("résultats d'outils : tronqués, erreurs distinguées, vides ignorés", () => {
const s = new UIStore();
s.toolCall("Read", "a.ts");
s.toolResult("Read", "x".repeat(1000), false);
s.toolResult("Bash", "", false);
s.toolResult("Bash", "échec", true);
const items = s.getSnapshot().items;
expect(items.map((i) => i.kind)).toEqual(["step", "result", "error"]);
expect(items[1]?.text.endsWith("… (tronqué)")).toBe(true);
});
it("interrupt : false sans gestionnaire, sinon l'appelle", () => {
const s = new UIStore();
expect(s.interrupt()).toBe(false);
let called = 0;
s.setInterruptHandler(() => called++);
expect(s.interrupt()).toBe(true);
expect(called).toBe(1);
s.setInterruptHandler(null);
expect(s.interrupt()).toBe(false);
});
it("commande shell : détail, échec signalé, longue sortie tronquée", () => {
const s = new UIStore();
s.shell("ls", "a\nb", null);
s.shell("false", "", "code de sortie : 1");
s.shell("cat gros", "x".repeat(10_000), null);
const [ok, ko, big] = s.getSnapshot().items;
expect(ok).toMatchObject({ kind: "shell", text: "ls", detail: "a\nb", failed: false });
expect(ko).toMatchObject({ detail: "(aucune sortie)\n[code de sortie : 1]", failed: true });
expect(big?.detail).toContain("caractères omis à l'affichage");
});
2026-10-11 22:41:17 +00:00
});