png.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { expect, test } from "bun:test"
  2. import { createCanvas, loadImage } from "@napi-rs/canvas"
  3. import { RGBA, TextAttributes, type CapturedFrame } from "@opentui/core"
  4. import { SimulationPng } from "../src/frontend/png"
  5. test("renders captured frames with bundled fonts", () => {
  6. const frame: CapturedFrame = {
  7. cols: 4,
  8. rows: 1,
  9. cursor: [0, 0],
  10. lines: [
  11. {
  12. spans: [
  13. {
  14. text: "Test",
  15. width: 4,
  16. fg: RGBA.fromInts(255, 255, 255),
  17. bg: RGBA.fromInts(0, 0, 0),
  18. attributes: TextAttributes.BOLD | TextAttributes.ITALIC,
  19. },
  20. ],
  21. },
  22. ],
  23. }
  24. const image = SimulationPng.screenshotFrame(frame)
  25. expect(image.width).toBe(40)
  26. expect(image.height).toBe(20)
  27. expect(image.data.subarray(1, 4).toString()).toBe("PNG")
  28. })
  29. test("renders OpenCode symbols instead of missing-glyph boxes", async () => {
  30. const pixels = async (text: string) => {
  31. const image = SimulationPng.screenshotFrame({
  32. cols: 1,
  33. rows: 1,
  34. cursor: [0, 0],
  35. lines: [
  36. {
  37. spans: [
  38. {
  39. text,
  40. width: 1,
  41. fg: RGBA.fromInts(255, 255, 255),
  42. bg: RGBA.fromInts(0, 0, 0),
  43. attributes: 0,
  44. },
  45. ],
  46. },
  47. ],
  48. })
  49. const canvas = createCanvas(image.width, image.height)
  50. const context = canvas.getContext("2d")
  51. context.drawImage(await loadImage(image.data), 0, 0)
  52. return [...context.getImageData(0, 0, image.width, image.height).data]
  53. }
  54. const missingGlyph = await pixels("\u{10ffff}")
  55. for (const symbol of ["△", "✱", "⇆", "⠹"]) {
  56. expect(await pixels(symbol)).not.toEqual(missingGlyph)
  57. }
  58. })
  59. test("fills adjacent block elements without glyph gaps", async () => {
  60. const image = SimulationPng.screenshotFrame({
  61. cols: 2,
  62. rows: 1,
  63. cursor: [0, 0],
  64. lines: [
  65. {
  66. spans: [
  67. {
  68. text: "▀▀",
  69. width: 2,
  70. fg: RGBA.fromInts(255, 255, 255),
  71. bg: RGBA.fromInts(0, 0, 0),
  72. attributes: 0,
  73. },
  74. ],
  75. },
  76. ],
  77. })
  78. const canvas = createCanvas(image.width, image.height)
  79. const context = canvas.getContext("2d")
  80. context.drawImage(await loadImage(image.data), 0, 0)
  81. expect([...context.getImageData(0, 5, image.width, 1).data]).toEqual(
  82. Array.from({ length: image.width }, () => [255, 255, 255, 255]).flat(),
  83. )
  84. expect([...context.getImageData(0, 15, image.width, 1).data]).toEqual(
  85. Array.from({ length: image.width }, () => [0, 0, 0, 255]).flat(),
  86. )
  87. })
  88. test("draws heavy vertical box elements on cell boundaries", async () => {
  89. const image = SimulationPng.screenshotFrame({
  90. cols: 1,
  91. rows: 2,
  92. cursor: [0, 0],
  93. lines: [
  94. {
  95. spans: [
  96. {
  97. text: "┃",
  98. width: 1,
  99. fg: RGBA.fromInts(255, 255, 255),
  100. bg: RGBA.fromInts(0, 0, 0),
  101. attributes: 0,
  102. },
  103. ],
  104. },
  105. {
  106. spans: [
  107. {
  108. text: "╹",
  109. width: 1,
  110. fg: RGBA.fromInts(255, 255, 255),
  111. bg: RGBA.fromInts(0, 0, 0),
  112. attributes: 0,
  113. },
  114. ],
  115. },
  116. ],
  117. })
  118. const canvas = createCanvas(image.width, image.height)
  119. const context = canvas.getContext("2d")
  120. context.drawImage(await loadImage(image.data), 0, 0)
  121. expect([...context.getImageData(4, 0, 2, 30).data]).toEqual(
  122. Array.from({ length: 60 }, () => [255, 255, 255, 255]).flat(),
  123. )
  124. expect([...context.getImageData(4, 30, 2, 10).data]).toEqual(Array.from({ length: 20 }, () => [0, 0, 0, 255]).flat())
  125. })