import { ApprovalRequestId, EnvironmentId, ThreadId } from "@t3tools/contracts";import { beforeEach, expect, it, vi } from "vite-plus/test";import { useComposerDraftStore } from "./composerDraftStore";import { questionAttachmentDraftId, questionAttachmentDraftPrefix, changeQuestionAttachmentPreparation, clearQuestionAttachmentDraft, useQuestionAttachmentPreparation,} from "./questionAttachments"; const release = vi.hoisted(() => vi.fn());vi.mock("./lib/attachmentUploadQueue", () => ({ releaseDraftAttachments: release }));const environmentId = EnvironmentId.make("environment-1");const threadId = ThreadId.make("thread-1");const requestId = ApprovalRequestId.make("request-1");beforeEach(() => { useComposerDraftStore.setState({ draftsByThreadKey: {}, draftThreadsByThreadKey: {} }); useQuestionAttachmentPreparation.setState({ counts: {} }); release.mockClear();});it("keeps question files separate from the normal draft and other questions", () => { const revoke = vi.spyOn(URL, "revokeObjectURL"); const store = useComposerDraftStore.getState(); const first = questionAttachmentDraftId(environmentId, threadId, requestId, "first"); const second = questionAttachmentDraftId(environmentId, threadId, requestId, "second"); store.setPrompt({ environmentId, threadId }, "Unsent prompt"); const image = { type: "image" as const, id: "image-1", name: "image.png", mimeType: "image/png", sizeBytes: 5, previewUrl: "blob:question-image", file: new File(["image"], "image.png", { type: "image/png" }), }; store.addImages(first, [image]); store.addFiles(second, [ { type: "file", id: "file-1", name: "spec.txt", mimeType: "text/plain", sizeBytes: 4, file: new File(["spec"], "spec.txt", { type: "text/plain" }), }, ]); changeQuestionAttachmentPreparation(first, 1); expect(store.getComposerDraft(first)?.images).toHaveLength(1); expect(store.getComposerDraft(second)?.files).toHaveLength(1); clearQuestionAttachmentDraft(first); expect(store.getComposerDraft(first)).toBeNull(); expect(store.getComposerDraft(second)?.files).toHaveLength(1); expect(store.getComposerDraft({ environmentId, threadId })?.prompt).toBe("Unsent prompt"); expect(useQuestionAttachmentPreparation.getState().counts[first]).toBeUndefined(); expect(release).toHaveBeenCalledWith([image]); expect(revoke).toHaveBeenCalledWith(image.previewUrl); revoke.mockRestore();});it("scopes provider request ids to their environment and thread", () => { const keys = [ questionAttachmentDraftId(environmentId, threadId, requestId, "q"), questionAttachmentDraftId(EnvironmentId.make("environment-2"), threadId, requestId, "q"), questionAttachmentDraftId(environmentId, ThreadId.make("thread-2"), requestId, "q"), ]; expect(new Set(keys).size).toBe(3);});it("does not match drafts belonging to threads with a shared id prefix", () => { const prefix = questionAttachmentDraftPrefix(environmentId, threadId); expect( questionAttachmentDraftId(environmentId, threadId, requestId, "q").startsWith(prefix), ).toBe(true); for (const suffix of ["-extra", ":extra", "/extra", "%extra"]) { expect( questionAttachmentDraftId( environmentId, ThreadId.make(`${threadId}${suffix}`), requestId, "q", ).startsWith(prefix), ).toBe(false); }}); it("does not clear another environment whose id contains a question prefix", () => { const otherEnvironment = EnvironmentId.make( `${environmentId}:question-${encodeURIComponent(JSON.stringify(threadId))}-nested`, ); const otherKey = questionAttachmentDraftId(otherEnvironment, threadId, requestId, "q"); const ownKey = questionAttachmentDraftId(environmentId, threadId, requestId, "q"); const store = useComposerDraftStore.getState(); store.setPrompt(otherKey, "Keep this answer"); store.setPrompt(ownKey, "Discard this answer"); const prefix = questionAttachmentDraftPrefix(environmentId, threadId); for (const key of [ownKey, otherKey]) { if (key.startsWith(prefix)) clearQuestionAttachmentDraft(key); } expect(store.getComposerDraft(ownKey)).toBeNull(); expect(store.getComposerDraft(otherKey)?.prompt).toBe("Keep this answer");});
import { ApprovalRequestId, EnvironmentId, ThreadId } from "@t3tools/contracts";
import { beforeEach, expect, it, vi } from "vite-plus/test";
import { useComposerDraftStore } from "./composerDraftStore";
import {
questionAttachmentDraftId,
questionAttachmentDraftPrefix,
changeQuestionAttachmentPreparation,
clearQuestionAttachmentDraft,
useQuestionAttachmentPreparation,
} from "./questionAttachments";
const release = vi.hoisted(() => vi.fn());
vi.mock("./lib/attachmentUploadQueue", () => ({ releaseDraftAttachments: release }));
const environmentId = EnvironmentId.make("environment-1");
const threadId = ThreadId.make("thread-1");
const requestId = ApprovalRequestId.make("request-1");
beforeEach(() => {
useComposerDraftStore.setState({ draftsByThreadKey: {}, draftThreadsByThreadKey: {} });
useQuestionAttachmentPreparation.setState({ counts: {} });
release.mockClear();
});
it("keeps question files separate from the normal draft and other questions", () => {
const revoke = vi.spyOn(URL, "revokeObjectURL");
const store = useComposerDraftStore.getState();
const first = questionAttachmentDraftId(environmentId, threadId, requestId, "first");
const second = questionAttachmentDraftId(environmentId, threadId, requestId, "second");
store.setPrompt({ environmentId, threadId }, "Unsent prompt");
const image = {
type: "image" as const,
id: "image-1",
name: "image.png",
mimeType: "image/png",
sizeBytes: 5,
previewUrl: "blob:question-image",
file: new File(["image"], "image.png", { type: "image/png" }),
};
store.addImages(first, [image]);
store.addFiles(second, [
{
type: "file",
id: "file-1",
name: "spec.txt",
mimeType: "text/plain",
sizeBytes: 4,
file: new File(["spec"], "spec.txt", { type: "text/plain" }),
},
]);
changeQuestionAttachmentPreparation(first, 1);
expect(store.getComposerDraft(first)?.images).toHaveLength(1);
expect(store.getComposerDraft(second)?.files).toHaveLength(1);
clearQuestionAttachmentDraft(first);
expect(store.getComposerDraft(first)).toBeNull();
expect(store.getComposerDraft(second)?.files).toHaveLength(1);
expect(store.getComposerDraft({ environmentId, threadId })?.prompt).toBe("Unsent prompt");
expect(useQuestionAttachmentPreparation.getState().counts[first]).toBeUndefined();
expect(release).toHaveBeenCalledWith([image]);
expect(revoke).toHaveBeenCalledWith(image.previewUrl);
revoke.mockRestore();
});
it("scopes provider request ids to their environment and thread", () => {
const keys = [
questionAttachmentDraftId(environmentId, threadId, requestId, "q"),
questionAttachmentDraftId(EnvironmentId.make("environment-2"), threadId, requestId, "q"),
questionAttachmentDraftId(environmentId, ThreadId.make("thread-2"), requestId, "q"),
];
expect(new Set(keys).size).toBe(3);
});
it("does not match drafts belonging to threads with a shared id prefix", () => {
const prefix = questionAttachmentDraftPrefix(environmentId, threadId);
expect(
questionAttachmentDraftId(environmentId, threadId, requestId, "q").startsWith(prefix),
).toBe(true);
for (const suffix of ["-extra", ":extra", "/extra", "%extra"]) {
expect(
questionAttachmentDraftId(
environmentId,
ThreadId.make(`${threadId}${suffix}`),
requestId,
"q",
).startsWith(prefix),
).toBe(false);
}
});
it("does not clear another environment whose id contains a question prefix", () => {
const otherEnvironment = EnvironmentId.make(
`${environmentId}:question-${encodeURIComponent(JSON.stringify(threadId))}-nested`,
);
const otherKey = questionAttachmentDraftId(otherEnvironment, threadId, requestId, "q");
const ownKey = questionAttachmentDraftId(environmentId, threadId, requestId, "q");
const store = useComposerDraftStore.getState();
store.setPrompt(otherKey, "Keep this answer");
store.setPrompt(ownKey, "Discard this answer");
const prefix = questionAttachmentDraftPrefix(environmentId, threadId);
for (const key of [ownKey, otherKey]) {
if (key.startsWith(prefix)) clearQuestionAttachmentDraft(key);
}
expect(store.getComposerDraft(ownKey)).toBeNull();
expect(store.getComposerDraft(otherKey)?.prompt).toBe("Keep this answer");
});