177 lines
4.9 KiB
TypeScript
177 lines
4.9 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import type { ComposerDraftStorage } from "./composer-drafts.js";
|
|
import {
|
|
COMPOSER_DRAFT_LIMIT,
|
|
COMPOSER_DRAFT_STORAGE_KEY,
|
|
clearComposerDraft,
|
|
composerDraftThreadKey,
|
|
EMPTY_COMPOSER_DRAFT,
|
|
isComposerDraftEmpty,
|
|
readComposerDraft,
|
|
resetMemoryComposerDrafts,
|
|
writeComposerDraft,
|
|
} from "./composer-drafts.js";
|
|
|
|
function createFakeStorage(): ComposerDraftStorage & { map: Map<string, string> } {
|
|
const map = new Map<string, string>();
|
|
return {
|
|
map,
|
|
getItem(key: string) {
|
|
return map.get(key) ?? null;
|
|
},
|
|
setItem(key: string, value: string) {
|
|
map.set(key, value);
|
|
},
|
|
removeItem(key: string) {
|
|
map.delete(key);
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("composer-drafts", () => {
|
|
beforeEach(() => {
|
|
resetMemoryComposerDrafts();
|
|
});
|
|
|
|
it("derives thread keys for bot and group conversations", () => {
|
|
expect(composerDraftThreadKey({ botId: "bot-1" })).toBe("bot:bot-1");
|
|
expect(composerDraftThreadKey({ groupId: "grp-1", botId: "bot-1" })).toBe("group:grp-1");
|
|
expect(composerDraftThreadKey({ botId: " ", groupId: null })).toBeUndefined();
|
|
});
|
|
|
|
it("detects empty vs non-empty drafts", () => {
|
|
expect(isComposerDraftEmpty(EMPTY_COMPOSER_DRAFT)).toBe(true);
|
|
expect(
|
|
isComposerDraftEmpty({
|
|
text: "Weiter an der Aufgabe schreiben",
|
|
selectedSkill: null,
|
|
selectedMentions: [],
|
|
}),
|
|
).toBe(false);
|
|
expect(
|
|
isComposerDraftEmpty({
|
|
text: "",
|
|
selectedSkill: {
|
|
id: "skill-1",
|
|
name: "research",
|
|
description: "Research skill",
|
|
source: "user",
|
|
readOnly: false,
|
|
},
|
|
selectedMentions: [],
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("persists and restores drafts across thread and page switches independently", () => {
|
|
const storage = createFakeStorage();
|
|
const botKey = composerDraftThreadKey({ botId: "alpha" });
|
|
const groupKey = composerDraftThreadKey({ groupId: "team-room" });
|
|
|
|
writeComposerDraft(
|
|
botKey,
|
|
{
|
|
text: "Entwurf im ersten Chat",
|
|
selectedSkill: {
|
|
id: "sk-1",
|
|
name: "summarize",
|
|
description: "Summarize notes",
|
|
source: "builtin",
|
|
readOnly: true,
|
|
},
|
|
selectedMentions: [
|
|
{
|
|
kind: "bot",
|
|
id: "helper-bot",
|
|
name: "Helper",
|
|
color: "#112233",
|
|
},
|
|
],
|
|
},
|
|
storage,
|
|
);
|
|
|
|
writeComposerDraft(
|
|
groupKey,
|
|
{
|
|
text: "Andere Nachricht in der Gruppe",
|
|
selectedSkill: null,
|
|
selectedMentions: [],
|
|
},
|
|
storage,
|
|
);
|
|
|
|
expect(readComposerDraft(botKey, storage)).toEqual({
|
|
text: "Entwurf im ersten Chat",
|
|
selectedSkill: {
|
|
id: "sk-1",
|
|
name: "summarize",
|
|
description: "Summarize notes",
|
|
source: "builtin",
|
|
readOnly: true,
|
|
},
|
|
selectedMentions: [
|
|
{
|
|
kind: "bot",
|
|
id: "helper-bot",
|
|
name: "Helper",
|
|
color: "#112233",
|
|
},
|
|
],
|
|
});
|
|
expect(readComposerDraft(groupKey, storage)).toEqual({
|
|
text: "Andere Nachricht in der Gruppe",
|
|
selectedSkill: null,
|
|
selectedMentions: [],
|
|
});
|
|
|
|
clearComposerDraft(botKey, storage);
|
|
expect(readComposerDraft(botKey, storage)).toEqual(EMPTY_COMPOSER_DRAFT);
|
|
expect(readComposerDraft(groupKey, storage).text).toBe("Andere Nachricht in der Gruppe");
|
|
|
|
clearComposerDraft(groupKey, storage);
|
|
expect(storage.map.has(COMPOSER_DRAFT_STORAGE_KEY)).toBe(false);
|
|
});
|
|
|
|
it("evicts oldest drafts beyond COMPOSER_DRAFT_LIMIT", () => {
|
|
const storage = createFakeStorage();
|
|
for (let index = 0; index < COMPOSER_DRAFT_LIMIT + 5; index += 1) {
|
|
writeComposerDraft(
|
|
`bot:b-${index}`,
|
|
{
|
|
text: `Draft ${index}`,
|
|
selectedSkill: null,
|
|
selectedMentions: [],
|
|
},
|
|
storage,
|
|
);
|
|
}
|
|
|
|
expect(readComposerDraft("bot:b-0", storage)).toEqual(EMPTY_COMPOSER_DRAFT);
|
|
expect(readComposerDraft("bot:b-4", storage)).toEqual(EMPTY_COMPOSER_DRAFT);
|
|
expect(readComposerDraft("bot:b-5", storage).text).toBe("Draft 5");
|
|
expect(readComposerDraft(`bot:b-${COMPOSER_DRAFT_LIMIT + 4}`, storage).text).toBe(
|
|
`Draft ${COMPOSER_DRAFT_LIMIT + 4}`,
|
|
);
|
|
});
|
|
|
|
it("falls back to in-memory storage when persistent storage is unavailable", () => {
|
|
const key = composerDraftThreadKey({ botId: "mobile-bot" });
|
|
writeComposerDraft(
|
|
key,
|
|
{
|
|
text: "Mobile Entwurf bleibt beim Tab-Wechsel erhalten",
|
|
selectedSkill: null,
|
|
selectedMentions: [],
|
|
},
|
|
null,
|
|
);
|
|
|
|
expect(readComposerDraft(key, null).text).toBe(
|
|
"Mobile Entwurf bleibt beim Tab-Wechsel erhalten",
|
|
);
|
|
clearComposerDraft(key, null);
|
|
expect(readComposerDraft(key, null)).toEqual(EMPTY_COMPOSER_DRAFT);
|
|
});
|
|
});
|