diff --git a/apps/mobile/src/features/home/useThreadListActions.test.ts b/apps/mobile/src/features/home/useThreadListActions.test.ts index 9313aef299881..5ff220a711aef 100644 --- a/apps/mobile/src/features/home/useThreadListActions.test.ts +++ b/apps/mobile/src/features/home/useThreadListActions.test.ts @@ -18,6 +18,7 @@ const state = vi.hoisted(() => ({ dropBusy: false, failedAction: null as string | null, archivedRefreshes: [] as string[], + onArchivedRefresh: undefined as (() => void) | undefined, scopes: new Map>(), shells: [] as EnvironmentThreadShell[], requests: [] as { @@ -55,6 +56,7 @@ vi.mock("../../components/ui/Dialog", () => ({ vi.mock("../archive/useArchivedThreadSnapshots", () => ({ refreshArchivedThreadsForEnvironment: (environmentId: string) => { state.archivedRefreshes.push(environmentId); + state.onArchivedRefresh?.(); }, })); vi.mock("../../state/session", () => ({ @@ -67,8 +69,9 @@ vi.mock("../../state/presentation", () => ({ vi.mock("../../state/server", () => ({ environmentServerConfigsAtom: "server-configs", })); -vi.mock("../../state/atom-registry", () => ({ - appAtomRegistry: { +vi.mock("../../state/atom-registry", async () => { + const registry = (await import("effect/reactivity")).AtomRegistry.make(); + const stub = { set: (_atom: string, value: boolean) => { state.dropBusy = value; }, @@ -97,8 +100,21 @@ vi.mock("../../state/atom-registry", () => ({ }, ]), ), - }, -})); + }; + // Real atoms such as composerDraftsAtom go to a real registry; strings are stubbed state. + return { + appAtomRegistry: { + get: (atom: unknown) => + typeof atom === "string" + ? stub.get(atom) + : registry.get(atom as Parameters[0]), + set: (atom: unknown, value: unknown) => + typeof atom === "string" + ? stub.set(atom, value as boolean) + : registry.set(atom as Parameters[0], value), + }, + }; +}); vi.mock("../../state/use-atom-command", () => ({ useAtomCommand: (command: unknown) => command, })); @@ -157,6 +173,8 @@ vi.mock("../../state/threads", () => ({ ), })); +import { appAtomRegistry } from "../../state/atom-registry"; +import { composerDraftsAtom } from "../../state/use-composer-drafts"; import { dismissThreadUndoNotice, readThreadUndoNotice } from "./thread-undo-notice"; import { useThreadListActions } from "./useThreadListActions"; @@ -189,6 +207,7 @@ beforeEach(() => { state.connectedEnvironmentIds = [primaryEnvironmentId, otherEnvironmentId]; state.failedAction = null; state.archivedRefreshes = []; + state.onArchivedRefresh = undefined; state.pendingOrder = null; state.dropBusy = false; state.scopes = new Map([ @@ -230,7 +249,9 @@ describe("thread list operation permissions", () => { expect(state.requests).toEqual([]); }); - it("rechecks permission after a retained delete confirmation", async () => { + it("rechecks permission after a retained delete confirmation, then clears the draft", async () => { + const draft = { text: "unsent", attachments: [] }; + appAtomRegistry.set(composerDraftsAtom, { "primary:thread": draft, "primary:other": draft }); useThreadListActions().confirmDeleteThread(makeThread()); const confirm = state.dialogs[0]?.onConfirm; expect(confirm).toBeTypeOf("function"); @@ -239,6 +260,15 @@ describe("thread list operation permissions", () => { await confirm!(); expect(state.requests).toEqual([]); + expect(Object.keys(appAtomRegistry.get(composerDraftsAtom))).toContain("primary:thread"); + + state.scopes.get(primaryEnvironmentId)!.add(AuthOrchestrationOperateScope); + const deleted = new Promise((resolve) => (state.onArchivedRefresh = resolve)); + confirm!(); + await deleted; + + expect(state.requests).toEqual([expect.objectContaining({ action: "delete" })]); + expect(Object.keys(appAtomRegistry.get(composerDraftsAtom))).toEqual(["primary:other"]); }); }); diff --git a/apps/mobile/src/features/home/useThreadListActions.ts b/apps/mobile/src/features/home/useThreadListActions.ts index 248c436905311..a5c38a86931aa 100644 --- a/apps/mobile/src/features/home/useThreadListActions.ts +++ b/apps/mobile/src/features/home/useThreadListActions.ts @@ -17,6 +17,7 @@ import { appAtomRegistry } from "../../state/atom-registry"; import { environmentServerConfigsAtom } from "../../state/server"; import { connectedEnvironmentIdsAtom } from "../../state/presentation"; import { readEnvironmentScope } from "../../state/session"; +import { clearComposerDraft } from "../../state/use-composer-drafts"; import { environmentThreadShells, threadEnvironment } from "../../state/threads"; import { queuedThreadKeysAtom } from "../../state/use-thread-outbox"; import { useAtomCommand } from "../../state/use-atom-command"; @@ -160,6 +161,7 @@ function useThreadActionExecutor( if (action === "archive" || action === "unarchive" || action === "delete") { refreshArchivedThreadsForEnvironment(thread.environmentId); } + if (action === "delete") clearComposerDraft(key); onCompleted?.(action, thread); return true; })();