import { describe, expect, it, vi } from "vitest"; import { clearFinalizableDraftMessage, createFinalizableDraftLifecycle, createFinalizableDraftStreamControlsForState, takeMessageIdAfterStop, } from "./draft-stream-controls.js"; describe("draft-stream-controls", () => { it("takeMessageIdAfterStop stops, reads, and clears message id", async () => { const events: string[] = []; let messageId: string | undefined = "m-1"; const result = await takeMessageIdAfterStop({ stopForClear: async () => { events.push("stop"); }, readMessageId: () => { events.push("read"); return messageId; }, clearMessageId: () => { events.push("clear"); messageId = undefined; }, }); expect(result).toBe("m-1"); expect(messageId).toBeUndefined(); expect(events).toEqual(["stop", "read", "clear"]); }); it("clearFinalizableDraftMessage deletes valid message ids", async () => { const deleteMessage = vi.fn(async () => {}); const onDeleteSuccess = vi.fn(); await clearFinalizableDraftMessage({ stopForClear: async () => {}, readMessageId: () => "m-2", clearMessageId: () => {}, isValidMessageId: (value): value is string => typeof value === "string", deleteMessage, onDeleteSuccess, warnPrefix: "cleanup failed", }); expect(deleteMessage).toHaveBeenCalledWith("m-2"); expect(onDeleteSuccess).toHaveBeenCalledWith("m-2"); }); it("clearFinalizableDraftMessage skips invalid message ids", async () => { const deleteMessage = vi.fn(async () => {}); await clearFinalizableDraftMessage({ stopForClear: async () => {}, readMessageId: () => 123, clearMessageId: () => {}, isValidMessageId: (value): value is string => typeof value === "string", deleteMessage, warnPrefix: "cleanup failed", }); expect(deleteMessage).not.toHaveBeenCalled(); }); it("clearFinalizableDraftMessage warns when delete fails", async () => { const warn = vi.fn(); await clearFinalizableDraftMessage({ stopForClear: async () => {}, readMessageId: () => "m-3", clearMessageId: () => {}, isValidMessageId: (value): value is string => typeof value === "string", deleteMessage: async () => { throw new Error("boom"); }, warn, warnPrefix: "cleanup failed", }); expect(warn).toHaveBeenCalledWith("cleanup failed: boom"); }); it("controls ignore updates after final", async () => { const sendOrEditStreamMessage = vi.fn(async () => true); const controls = createFinalizableDraftStreamControlsForState({ throttleMs: 250, state: { stopped: false, final: true }, sendOrEditStreamMessage, }); controls.update("ignored"); await controls.loop.flush(); expect(sendOrEditStreamMessage).not.toHaveBeenCalled(); }); it("lifecycle clear marks stopped, clears id, and deletes preview message", async () => { const state = { stopped: false, final: false }; let messageId: string | undefined = "m-4"; const deleteMessage = vi.fn(async () => {}); const lifecycle = createFinalizableDraftLifecycle({ throttleMs: 250, state, sendOrEditStreamMessage: async () => true, readMessageId: () => messageId, clearMessageId: () => { messageId = undefined; }, isValidMessageId: (value): value is string => typeof value === "string", deleteMessage, warnPrefix: "cleanup failed", }); await lifecycle.clear(); expect(state.stopped).toBe(true); expect(messageId).toBeUndefined(); expect(deleteMessage).toHaveBeenCalledWith("m-4"); }); });