import { afterEach, describe, expect, it } from "@effect/vitest";import { EnvironmentId, ProviderInstanceId } from "@t3tools/contracts"; import { appAtomRegistry } from "./atom-registry";import { clearComposerDraftContentState, composerDraftsAtom, decodePersistedComposerDrafts, type ComposerDraft, getComposerDraftSnapshot, removeComposerDraftsForEnvironment,} from "./use-composer-drafts"; const DRAFT: ComposerDraft = { text: "hello", attachments: [],}; afterEach(() => { appAtomRegistry.set(composerDraftsAtom, {});}); describe("mobile composer drafts", () => { it("hydrates selector state even when the message content is empty", () => { expect( decodePersistedComposerDrafts({ schemaVersion: 1, drafts: { "new-task:environment-1:project-1": { text: "", attachments: [], modelSelection: { instanceId: "codex", model: "gpt-5.4", options: [{ id: "reasoningEffort", value: "xhigh" }], }, runtimeMode: "approval-required", interactionMode: "plan", workspaceSelection: { mode: "worktree", branch: "main", worktreePath: null, }, }, }, }), ).toEqual({ "new-task:environment-1:project-1": { text: "", attachments: [], modelSelection: { instanceId: "codex", model: "gpt-5.4", options: [{ id: "reasoningEffort", value: "xhigh" }], }, runtimeMode: "approval-required", interactionMode: "plan", workspaceSelection: { mode: "worktree", branch: "main", worktreePath: null, }, }, }); }); it("keeps legacy content-only drafts and rejects invalid selector state", () => { expect( decodePersistedComposerDrafts({ schemaVersion: 1, drafts: { "environment-1:thread-1": DRAFT, }, }), ).toEqual({ "environment-1:thread-1": DRAFT, }); expect(() => decodePersistedComposerDrafts({ schemaVersion: 1, drafts: { "environment-1:thread-1": { ...DRAFT, runtimeMode: "sometimes-safe", }, }, }), ).toThrow(); }); it("clears sent content without clearing the selected model or workspace", () => { const draftKey = "environment-1:thread-1"; const draft: ComposerDraft = { text: "send this", attachments: [], modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", options: [{ id: "reasoningEffort", value: "xhigh" }], }, workspaceSelection: { mode: "worktree", branch: "main", worktreePath: null, }, }; expect(clearComposerDraftContentState({ [draftKey]: draft }, draftKey)).toEqual({ [draftKey]: { ...draft, text: "", attachments: [], }, }); }); it("reads the latest selector state synchronously for send", () => { const draftKey = "environment-1:thread-1"; const selectedDraft: ComposerDraft = { text: "send this", attachments: [], modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", options: [{ id: "reasoningEffort", value: "xhigh" }], }, }; appAtomRegistry.set(composerDraftsAtom, { [draftKey]: selectedDraft }); expect(getComposerDraftSnapshot(draftKey)).toEqual(selectedDraft); }); it("removes only drafts owned by the selected environment", () => { const environmentId = EnvironmentId.make("environment-cloud"); const retainedEnvironmentId = EnvironmentId.make("environment-local"); expect( removeComposerDraftsForEnvironment( { [`${environmentId}:thread-cloud`]: DRAFT, [`new-task:${environmentId}:project-cloud`]: DRAFT, [`${retainedEnvironmentId}:thread-local`]: DRAFT, [`new-task:${retainedEnvironmentId}:project-local`]: DRAFT, }, environmentId, ), ).toEqual({ [`${retainedEnvironmentId}:thread-local`]: DRAFT, [`new-task:${retainedEnvironmentId}:project-local`]: DRAFT, }); });});
import { afterEach, describe, expect, it } from "@effect/vitest";
import { EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
import { appAtomRegistry } from "./atom-registry";
import {
clearComposerDraftContentState,
composerDraftsAtom,
decodePersistedComposerDrafts,
type ComposerDraft,
getComposerDraftSnapshot,
removeComposerDraftsForEnvironment,
} from "./use-composer-drafts";
const DRAFT: ComposerDraft = {
text: "hello",
attachments: [],
};
afterEach(() => {
appAtomRegistry.set(composerDraftsAtom, {});
});
describe("mobile composer drafts", () => {
it("hydrates selector state even when the message content is empty", () => {
expect(
decodePersistedComposerDrafts({
schemaVersion: 1,
drafts: {
"new-task:environment-1:project-1": {
text: "",
attachments: [],
modelSelection: {
instanceId: "codex",
model: "gpt-5.4",
options: [{ id: "reasoningEffort", value: "xhigh" }],
},
runtimeMode: "approval-required",
interactionMode: "plan",
workspaceSelection: {
mode: "worktree",
branch: "main",
worktreePath: null,
},
},
},
}),
).toEqual({
"new-task:environment-1:project-1": {
text: "",
attachments: [],
modelSelection: {
instanceId: "codex",
model: "gpt-5.4",
options: [{ id: "reasoningEffort", value: "xhigh" }],
},
runtimeMode: "approval-required",
interactionMode: "plan",
workspaceSelection: {
mode: "worktree",
branch: "main",
worktreePath: null,
},
},
});
});
it("keeps legacy content-only drafts and rejects invalid selector state", () => {
expect(
decodePersistedComposerDrafts({
schemaVersion: 1,
drafts: {
"environment-1:thread-1": DRAFT,
},
}),
).toEqual({
"environment-1:thread-1": DRAFT,
});
expect(() =>
decodePersistedComposerDrafts({
schemaVersion: 1,
drafts: {
"environment-1:thread-1": {
...DRAFT,
runtimeMode: "sometimes-safe",
},
},
}),
).toThrow();
});
it("clears sent content without clearing the selected model or workspace", () => {
const draftKey = "environment-1:thread-1";
const draft: ComposerDraft = {
text: "send this",
attachments: [],
modelSelection: {
instanceId: ProviderInstanceId.make("codex"),
model: "gpt-5.4",
options: [{ id: "reasoningEffort", value: "xhigh" }],
},
workspaceSelection: {
mode: "worktree",
branch: "main",
worktreePath: null,
},
};
expect(clearComposerDraftContentState({ [draftKey]: draft }, draftKey)).toEqual({
[draftKey]: {
...draft,
text: "",
attachments: [],
},
});
});
it("reads the latest selector state synchronously for send", () => {
const draftKey = "environment-1:thread-1";
const selectedDraft: ComposerDraft = {
text: "send this",
attachments: [],
modelSelection: {
instanceId: ProviderInstanceId.make("codex"),
model: "gpt-5.4",
options: [{ id: "reasoningEffort", value: "xhigh" }],
},
};
appAtomRegistry.set(composerDraftsAtom, { [draftKey]: selectedDraft });
expect(getComposerDraftSnapshot(draftKey)).toEqual(selectedDraft);
});
it("removes only drafts owned by the selected environment", () => {
const environmentId = EnvironmentId.make("environment-cloud");
const retainedEnvironmentId = EnvironmentId.make("environment-local");
expect(
removeComposerDraftsForEnvironment(
{
[`${environmentId}:thread-cloud`]: DRAFT,
[`new-task:${environmentId}:project-cloud`]: DRAFT,
[`${retainedEnvironmentId}:thread-local`]: DRAFT,
[`new-task:${retainedEnvironmentId}:project-local`]: DRAFT,
},
environmentId,
),
).toEqual({
[`${retainedEnvironmentId}:thread-local`]: DRAFT,
[`new-task:${retainedEnvironmentId}:project-local`]: DRAFT,
});
});
});