tab-pulse.test.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /** @jsxImportSource @opentui/solid */
  2. import { expect, test } from "bun:test"
  3. import { RGBA } from "@opentui/core"
  4. import { testRender } from "@opentui/solid"
  5. import { createSignal } from "solid-js"
  6. import {
  7. TabPulse,
  8. blendTabPulseColor,
  9. completionPulseOpacity,
  10. glowIgnitionLevel,
  11. unreadGlowIntensity,
  12. } from "../../src/component/tab-pulse"
  13. import { tint } from "../../src/theme/color"
  14. test("a prompt pulse restarts the neutral edge flash while the tab remains busy", async () => {
  15. const background = RGBA.fromHex("#101010")
  16. const flash = RGBA.fromHex("#f0f0f0")
  17. const [promptPulse, setPromptPulse] = createSignal(0)
  18. const app = await testRender(
  19. () => (
  20. <box width={8} height={1} backgroundColor={background}>
  21. <TabPulse
  22. active={true}
  23. promptPulse={promptPulse()}
  24. color={background}
  25. flashColor={flash}
  26. backgroundColor={background}
  27. />
  28. </box>
  29. ),
  30. { width: 8, height: 1 },
  31. )
  32. const firstBackground = () => app.captureSpans().lines[0]?.spans[0]?.bg
  33. try {
  34. await app.renderOnce()
  35. expect(firstBackground()?.equals(background)).toBeTrue()
  36. setPromptPulse(1)
  37. await Bun.sleep(80)
  38. await app.renderOnce()
  39. expect(firstBackground()?.equals(background)).toBeFalse()
  40. expect(firstBackground()?.r ?? 0).toBeGreaterThan(0.17)
  41. await Bun.sleep(800)
  42. await app.renderOnce()
  43. expect(firstBackground()?.equals(background)).toBeTrue()
  44. setPromptPulse(2)
  45. await Bun.sleep(80)
  46. await app.renderOnce()
  47. expect(firstBackground()?.equals(background)).toBeFalse()
  48. } finally {
  49. app.renderer.destroy()
  50. }
  51. })
  52. test("completion pulse rises quickly and fades over the remaining duration", () => {
  53. expect(completionPulseOpacity(0)).toBe(0)
  54. expect(completionPulseOpacity(0.06)).toBeCloseTo(0.5)
  55. expect(completionPulseOpacity(0.12)).toBe(1)
  56. expect(completionPulseOpacity(0.56)).toBeCloseTo(0.5)
  57. expect(completionPulseOpacity(1)).toBe(0)
  58. })
  59. test("glow ignition overshoots the resting level and settles back to it", () => {
  60. expect(glowIgnitionLevel(0)).toBe(0)
  61. expect(glowIgnitionLevel(0.3)).toBeCloseTo(1.5)
  62. expect(glowIgnitionLevel(0.6)).toBeGreaterThan(1)
  63. expect(glowIgnitionLevel(1)).toBe(1)
  64. })
  65. test("unread glow peaks behind the tab number and fades to the normal background", () => {
  66. const intensities = Array.from({ length: 22 }, (_, index) => unreadGlowIntensity(index, 22))
  67. expect(intensities[0]).toBe(1)
  68. expect(intensities[1]).toBe(1)
  69. expect(intensities[2]).toBeLessThan(1)
  70. expect(intensities.slice(1)).toEqual(intensities.slice(1).sort((a, b) => b - a))
  71. expect(intensities[13]).toBe(0)
  72. expect(intensities.at(-1)).toBe(0)
  73. })
  74. test("unread glow reaches the normal background on compact tabs", () => {
  75. expect(unreadGlowIntensity(0, 8)).toBe(1)
  76. expect(unreadGlowIntensity(7, 8)).toBe(0)
  77. })
  78. test("reuses a color while preserving the original glow and pulse blend stages", () => {
  79. const output = RGBA.fromInts(0, 0, 0)
  80. const background = RGBA.fromHex("#1a1b26")
  81. const glowColor = RGBA.fromHex("#82aaff")
  82. const runningColor = RGBA.fromHex("#c8d3f5")
  83. const flashColor = RGBA.fromHex("#e2e8fb")
  84. const completionColor = RGBA.fromHex("#ff9e64")
  85. for (const glow of [0, 0.08, 0.16]) {
  86. for (const running of [0, 0.01, 0.07, 0.14]) {
  87. for (const flash of [0, 0.05, 0.1]) {
  88. for (const completion of [0, 0.03, 0.09, 0.18]) {
  89. blendTabPulseColor(
  90. output,
  91. background,
  92. glowColor,
  93. runningColor,
  94. flashColor,
  95. completionColor,
  96. glow,
  97. running,
  98. flash,
  99. completion,
  100. )
  101. expect(output.buffer).toEqual(
  102. tint(
  103. tint(tint(tint(background, glowColor, glow), runningColor, running), flashColor, flash),
  104. completionColor,
  105. completion,
  106. ).buffer,
  107. )
  108. }
  109. }
  110. }
  111. }
  112. })