prompt-attachments.test.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { describe, expect, test } from "bun:test"
  2. import { createRoot } from "solid-js"
  3. import { createPromptAttachmentsCore } from "@/components/prompt-input/attachments"
  4. import { createPromptState } from "@/context/prompt"
  5. describe("prompt attachment session ownership", () => {
  6. test("adds an asynchronously read image to the session where the read started", async () => {
  7. await createRoot(async (dispose) => {
  8. const sessions = { A: createPromptState(), B: createPromptState() }
  9. let active: "A" | "B" = "A"
  10. const attachments = createPromptAttachmentsCore({
  11. capture: () => sessions[active].capture(),
  12. editor: () => document.createElement("div"),
  13. })
  14. const pending = attachments.addAttachment(new File([new Uint8Array(1024 * 1024)], "a.png", { type: "image/png" }))
  15. active = "B"
  16. await pending
  17. expect(images(sessions.A)).toHaveLength(1)
  18. expect(images(sessions.B)).toHaveLength(0)
  19. dispose()
  20. })
  21. })
  22. test("finishes the captured attachment after the active editor is removed", async () => {
  23. await createRoot(async (dispose) => {
  24. const prompt = createPromptState()
  25. let editor: HTMLDivElement | undefined = document.createElement("div")
  26. const attachments = createPromptAttachmentsCore({
  27. capture: prompt.capture,
  28. editor: () => editor,
  29. })
  30. const pending = attachments.addAttachment(new File([new Uint8Array(1024 * 1024)], "a.png", { type: "image/png" }))
  31. editor = undefined
  32. await pending
  33. expect(images(prompt)).toHaveLength(1)
  34. dispose()
  35. })
  36. })
  37. test("keeps every file in a batch on the session where the batch started", async () => {
  38. await createRoot(async (dispose) => {
  39. const sessions = { A: createPromptState(), B: createPromptState() }
  40. let active: "A" | "B" = "A"
  41. const attachments = createPromptAttachmentsCore({
  42. capture: () => sessions[active].capture(),
  43. editor: () => document.createElement("div"),
  44. })
  45. const pending = attachments.addAttachments([
  46. new File([new Uint8Array(1024 * 1024)], "first.png", { type: "image/png" }),
  47. new File([new Uint8Array(1024 * 1024)], "second.png", { type: "image/png" }),
  48. ])
  49. active = "B"
  50. await pending
  51. expect(images(sessions.A)).toHaveLength(2)
  52. expect(images(sessions.B)).toHaveLength(0)
  53. dispose()
  54. })
  55. })
  56. test("keeps a delayed native clipboard image on the session where paste started", async () => {
  57. await createRoot(async (dispose) => {
  58. const sessions = { A: createPromptState(), B: createPromptState() }
  59. const read = Promise.withResolvers<File | null>()
  60. let active: "A" | "B" = "A"
  61. const attachments = createPromptAttachmentsCore({
  62. capture: () => sessions[active].capture(),
  63. editor: () => document.createElement("div"),
  64. })
  65. const pending = attachments.addClipboardAttachment(read.promise)
  66. active = "B"
  67. read.resolve(new File([new Uint8Array(1024 * 1024)], "clipboard.png", { type: "image/png" }))
  68. await pending
  69. expect(images(sessions.A)).toHaveLength(1)
  70. expect(images(sessions.B)).toHaveLength(0)
  71. dispose()
  72. })
  73. })
  74. })
  75. function images(prompt: ReturnType<typeof createPromptState>) {
  76. return prompt.current().filter((part) => part.type === "image")
  77. }