109 lines · 4.0 KB · TypeScript
RawDownload
import { beforeEach, describe, expect, it, vi } from "vite-plus/test";import { CommandId, EnvironmentId, MessageId, ThreadId } from "@t3tools/contracts";import type { QueuedThreadMessage } from "./thread-outbox-model"; const state = vi.hoisted(() => ({  dispatching: null as string | null,  held: {} as Record<string, true>,  draft: { text: "Existing draft", attachments: [] as { id: string }[] },  confirm: vi.fn(async () => true),  remove: vi.fn(async () => true),  flush: vi.fn(async () => {}),}));vi.mock("./atom-registry", () => ({  appAtomRegistry: {    get: (atom: string) => (atom === "dispatching" ? state.dispatching : state.held),  },}));vi.mock("./use-thread-outbox", () => ({  dispatchingQueuedMessageIdAtom: "dispatching",  editingQueuedMessageIdsAtom: "editing",  holdEditingQueuedMessage: (id: string) => {    state.held[id] = true;  },  releaseEditingQueuedMessage: (id: string) => {    delete state.held[id];  },}));vi.mock("./thread-outbox", () => ({  confirmThreadOutboxMessageQueued: state.confirm,  threadOutboxRevision: () => 1,}));vi.mock("./thread-outbox-removal", () => ({ removeThreadOutboxMessage: state.remove }));vi.mock("./use-composer-drafts", () => ({  waitForComposerDraftsLoaded: async () => {},  getComposerDraftSnapshot: () => state.draft,  mergeComposerDraftContent: async (_key: string, message: QueuedThreadMessage) => {    state.draft = {      text: `${state.draft.text}\n\n${message.text}`,      attachments: [...state.draft.attachments, ...message.attachments],    };  },  updateComposerDraftSettings: () => {},  flushComposerDrafts: state.flush,  undoComposerDraftMerge: async (_key: string, snapshot: typeof state.draft) => {    state.draft = snapshot;  },}));import { editPendingThreadMessage } from "./edit-pending-thread-message"; const message: QueuedThreadMessage = {  environmentId: EnvironmentId.make("env"),  threadId: ThreadId.make("thread"),  messageId: MessageId.make("message"),  commandId: CommandId.make("command"),  text: "Queued task",  createdAt: "2026-09-06T10:00:00.000Z",  attachments: [    {      id: "file",      type: "file",      name: "notes.txt",      mimeType: "text/plain",      sizeBytes: 10,      fileUri: "file:///notes.txt",    },  ],};beforeEach(() => {  vi.clearAllMocks();  state.dispatching = null;  state.held = {};  state.draft = { text: "Existing draft", attachments: [] };  state.confirm.mockResolvedValue(true);  state.remove.mockResolvedValue(true);  state.flush.mockResolvedValue(undefined);});describe("editing a pending message", () => {  it("locks delivery and persists text and attachments before removing the queued copy", async () => {    state.confirm.mockImplementationOnce(async () => {      expect(state.held[message.messageId]).toBe(true);      return true;    });    state.remove.mockImplementationOnce(async () => {      expect(state.flush).toHaveBeenCalled();      expect(state.draft.text).toBe("Existing draft\n\nQueued task");      expect(state.draft.attachments).toEqual(message.attachments);      return true;    });    expect(await editPendingThreadMessage(message)).toBe(true);    expect(state.held).toEqual({});  });  it("does not reclaim a message already being dispatched", async () => {    state.dispatching = message.messageId;    expect(await editPendingThreadMessage(message)).toBe(false);    expect(state.confirm).not.toHaveBeenCalled();    expect(state.draft.text).toBe("Existing draft");  });  it("rolls back the draft if removing the queued message fails", async () => {    state.remove.mockRejectedValueOnce(new Error("disk error"));    await expect(editPendingThreadMessage(message)).rejects.toThrow("disk error");    expect(state.draft).toEqual({ text: "Existing draft", attachments: [] });    expect(state.held).toEqual({});  });  it("rolls back when a newer queue revision wins", async () => {    state.remove.mockResolvedValueOnce(false);    expect(await editPendingThreadMessage(message)).toBe(false);    expect(state.draft.text).toBe("Existing draft");  });});