| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- import { describe, expect, test } from "bun:test"
- import type { AsyncStorage } from "@solid-primitives/storage"
- import { createEffect, createRoot } from "solid-js"
- import type { Platform } from "@/context/platform"
- import { createPromptReady, createPromptSession } from "@/context/prompt-state"
- import { ServerScope } from "@/utils/server-scope"
- import { createDraftStore } from "@/utils/draft-store"
- let read: ((value: string | null) => void) | undefined
- const storage: AsyncStorage = {
- getItem: () => new Promise((resolve) => (read = resolve)),
- setItem: async () => undefined,
- removeItem: async () => undefined,
- clear: async () => undefined,
- key: async () => null,
- getLength: async () => 0,
- length: Promise.resolve(0),
- }
- const platform: Platform = {
- platform: "web",
- openExternal: () => undefined,
- restart: async () => undefined,
- notify: async () => undefined,
- draftStore: {
- ...storage,
- putBlob: async () => {
- throw new Error("putBlob is not used by this test")
- },
- },
- }
- describe("prompt persistence", () => {
- test("waits for an async draft to hydrate before reporting ready", async () => {
- await new Promise<void>((resolve, reject) => {
- createRoot((dispose) => {
- const session = createPromptSession(ServerScope.local, { draftID: "draft-async" }, undefined, platform)
- const ready = createPromptReady(() => session)
- expect(ready()).toBe(false)
- expect(session.current()[0]).toMatchObject({ type: "text", content: "" })
- read?.(
- JSON.stringify({
- prompt: [{ type: "text", content: "persisted draft", start: 0, end: 15 }],
- cursor: 15,
- context: { items: [] },
- }),
- )
- createEffect(() => {
- if (!ready()) return
- try {
- expect(session.current()[0]).toMatchObject({ type: "text", content: "persisted draft" })
- dispose()
- resolve()
- } catch (error) {
- dispose()
- reject(error)
- }
- })
- })
- })
- })
- })
- test("moves legacy image data URLs into blobs and hydrates object URLs", async () => {
- const documents = new Map<string, string>()
- const blobs = new Map<string, Blob>()
- const store = createDraftStore({
- get: async (key) => documents.get(key) ?? null,
- set: async (key, value) => void documents.set(key, value),
- remove: async (key) => void documents.delete(key),
- putBlob: async (blob) => {
- const id = String(blob.size)
- blobs.set(id, blob)
- return id
- },
- getBlob: async (id) => blobs.get(id) ?? null,
- })
- await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }))
- expect(documents.get("prompt")).not.toContain("dataUrl")
- const value = JSON.parse((await store.getItem("prompt"))!)
- expect(value.prompt[0].blob.id).toBe("1")
- expect(value.prompt[0].blob.url).toStartWith("blob:")
- })
- test("does not let delayed blob migration overwrite a newer draft", async () => {
- const documents = new Map<string, string>()
- const migration = Promise.withResolvers<void>()
- const store = createDraftStore({
- get: async () => null,
- set: async (key, value) => void documents.set(key, value),
- remove: async () => undefined,
- putBlob: async () => {
- await migration.promise
- return "blob"
- },
- getBlob: async () => null,
- })
- const older = store.setItem(
- "prompt",
- JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }),
- )
- await Bun.sleep(0)
- await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "text", content: "latest" }] }))
- migration.resolve()
- await older
- expect(documents.get("prompt")).toContain("latest")
- })
|