tab-pulse.test.tsx 4.1 KB

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