clipboard.test.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { expect, test } from "bun:test"
  2. import {
  3. createClipboard,
  4. type ClipboardReadOptions,
  5. type ClipboardReadResult,
  6. type ClipboardService as CoreClipboardService,
  7. type ClipboardWriteOptions,
  8. type ClipboardWriteResult,
  9. type HostClipboardService,
  10. } from "@opentui/core"
  11. import { createClipboardAdapter } from "../src/clipboard"
  12. function coreClipboard(options: {
  13. read?: ClipboardReadResult
  14. write?: ClipboardWriteResult
  15. onRead?: (input: ClipboardReadOptions) => void
  16. onWrite?: (text: string, input: ClipboardWriteOptions) => void
  17. }): CoreClipboardService {
  18. return {
  19. async read(input) {
  20. options.onRead?.(input)
  21. return options.read ?? { status: "empty" }
  22. },
  23. async writeText(text, input) {
  24. options.onWrite?.(text, input)
  25. return (
  26. options.write ?? {
  27. host: { status: "written" },
  28. terminal: { status: "not-attempted", capability: "unknown" },
  29. }
  30. )
  31. },
  32. async clear() {
  33. return {
  34. host: { status: "cleared" },
  35. terminal: { status: "not-attempted", capability: "unknown" },
  36. }
  37. },
  38. async dispose() {},
  39. }
  40. }
  41. test("adapts OpenTUI image and text reads", async () => {
  42. const requests: ClipboardReadOptions[] = []
  43. const image = createClipboardAdapter(
  44. coreClipboard({
  45. read: {
  46. status: "read",
  47. representation: { mimeType: "image/png", bytes: new Uint8Array([0, 1, 2, 255]) },
  48. },
  49. onRead: (input) => requests.push(input),
  50. }),
  51. )
  52. const text = "line 1\r\n\t世界"
  53. const plain = createClipboardAdapter(
  54. coreClipboard({
  55. read: {
  56. status: "read",
  57. representation: { mimeType: "text/plain", bytes: new TextEncoder().encode(text) },
  58. },
  59. }),
  60. )
  61. expect(await image.read()).toEqual({ data: "AAEC/w==", mime: "image/png" })
  62. expect(await plain.read()).toEqual({ data: text, mime: "text/plain" })
  63. expect(requests).toEqual([{ preferredTypes: ["image/png", "text/plain"], selection: "clipboard" }])
  64. })
  65. test("uses all available routes but skips the process host remotely", async () => {
  66. const writes = { host: 0, terminal: 0 }
  67. const host: HostClipboardService = {
  68. maxWriteBytes: 8 * 1024 * 1024,
  69. async read() {
  70. return { status: "empty" }
  71. },
  72. async writeText() {
  73. writes.host++
  74. return { status: "written" }
  75. },
  76. async clear() {
  77. return { status: "cleared" }
  78. },
  79. async dispose() {},
  80. }
  81. const clipboard = createClipboardAdapter(
  82. createClipboard({
  83. host,
  84. terminal: {
  85. remote: true,
  86. writeText() {
  87. writes.terminal++
  88. return { status: "attempted", capability: "supported" }
  89. },
  90. clear() {
  91. return { status: "attempted", capability: "supported" }
  92. },
  93. },
  94. }),
  95. )
  96. expect(await clipboard.write("hello")).toBeUndefined()
  97. expect(writes).toEqual({ host: 0, terminal: 1 })
  98. })
  99. test("rejects only when no clipboard route accepted the write", async () => {
  100. const writes: [string, ClipboardWriteOptions][] = []
  101. const failure = new Error("native clipboard failed")
  102. const fallback = createClipboardAdapter(
  103. coreClipboard({
  104. write: {
  105. host: { status: "written" },
  106. terminal: { status: "local-failure", capability: "supported" },
  107. },
  108. }),
  109. )
  110. const clipboard = createClipboardAdapter(
  111. coreClipboard({
  112. write: {
  113. host: { status: "failed", error: failure },
  114. terminal: { status: "local-failure", capability: "supported" },
  115. },
  116. onWrite: (text, input) => writes.push([text, input]),
  117. }),
  118. )
  119. expect(await fallback.write("hello")).toBeUndefined()
  120. expect(await clipboard.write("hello").then(undefined, (error) => error)).toBe(failure)
  121. expect(writes).toEqual([["hello", { destination: "all-available", selection: "clipboard" }]])
  122. })