1
0

prompt-persistence.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { beforeAll, describe, expect, mock, test } from "bun:test"
  2. import type { AsyncStorage } from "@solid-primitives/storage"
  3. import { createEffect, createRoot } from "solid-js"
  4. import { ServerScope } from "@/utils/server-scope"
  5. import { createDraftStore } from "@/utils/draft-store"
  6. let Prompt: typeof import("@/context/prompt")
  7. let read: ((value: string | null) => void) | undefined
  8. const storage: AsyncStorage = {
  9. getItem: () => new Promise((resolve) => (read = resolve)),
  10. setItem: async () => undefined,
  11. removeItem: async () => undefined,
  12. clear: async () => undefined,
  13. key: async () => null,
  14. getLength: async () => 0,
  15. length: Promise.resolve(0),
  16. }
  17. beforeAll(async () => {
  18. mock.module("@solidjs/router", () => ({
  19. useParams: () => ({}),
  20. useSearchParams: () => [{}],
  21. useLocation: () => ({ pathname: "", query: {} }),
  22. useNavigate: () => () => undefined,
  23. }))
  24. mock.module("@opencode-ai/ui/context", () => ({
  25. createSimpleContext: () => ({
  26. use: () => undefined,
  27. provider: () => undefined,
  28. }),
  29. }))
  30. mock.module("@/context/platform", () => ({
  31. usePlatform: () => ({ platform: "desktop", storage: () => storage, draftStore: storage }),
  32. }))
  33. Prompt = await import("@/context/prompt")
  34. })
  35. describe("prompt persistence", () => {
  36. test("waits for an async draft to hydrate before reporting ready", async () => {
  37. await new Promise<void>((resolve, reject) => {
  38. createRoot((dispose) => {
  39. const session = Prompt.createPromptSession(ServerScope.local, { draftID: "draft-async" })
  40. const ready = Prompt.createPromptReady(() => session)
  41. expect(ready()).toBe(false)
  42. expect(session.current()[0]).toMatchObject({ type: "text", content: "" })
  43. read?.(
  44. JSON.stringify({
  45. prompt: [{ type: "text", content: "persisted draft", start: 0, end: 15 }],
  46. cursor: 15,
  47. context: { items: [] },
  48. }),
  49. )
  50. createEffect(() => {
  51. if (!ready()) return
  52. try {
  53. expect(session.current()[0]).toMatchObject({ type: "text", content: "persisted draft" })
  54. dispose()
  55. resolve()
  56. } catch (error) {
  57. dispose()
  58. reject(error)
  59. }
  60. })
  61. })
  62. })
  63. })
  64. })
  65. test("moves legacy image data URLs into blobs and hydrates object URLs", async () => {
  66. const documents = new Map<string, string>()
  67. const blobs = new Map<string, Blob>()
  68. const store = createDraftStore({
  69. get: async (key) => documents.get(key) ?? null,
  70. set: async (key, value) => void documents.set(key, value),
  71. remove: async (key) => void documents.delete(key),
  72. putBlob: async (blob) => {
  73. const id = String(blob.size)
  74. blobs.set(id, blob)
  75. return id
  76. },
  77. getBlob: async (id) => blobs.get(id) ?? null,
  78. })
  79. await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }))
  80. expect(documents.get("prompt")).not.toContain("dataUrl")
  81. const value = JSON.parse((await store.getItem("prompt"))!)
  82. expect(value.prompt[0].blob.id).toBe("1")
  83. expect(value.prompt[0].blob.url).toStartWith("blob:")
  84. })
  85. test("does not let delayed blob migration overwrite a newer draft", async () => {
  86. const documents = new Map<string, string>()
  87. const migration = Promise.withResolvers<void>()
  88. const store = createDraftStore({
  89. get: async () => null,
  90. set: async (key, value) => void documents.set(key, value),
  91. remove: async () => undefined,
  92. putBlob: async () => {
  93. await migration.promise
  94. return "blob"
  95. },
  96. getBlob: async () => null,
  97. })
  98. const older = store.setItem(
  99. "prompt",
  100. JSON.stringify({ prompt: [{ type: "image", dataUrl: "data:image/png;base64,YQ==" }] }),
  101. )
  102. await Bun.sleep(0)
  103. await store.setItem("prompt", JSON.stringify({ prompt: [{ type: "text", content: "latest" }] }))
  104. migration.resolve()
  105. await older
  106. expect(documents.get("prompt")).toContain("latest")
  107. })