app-lifecycle.test.tsx 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import { expect, mock, test } from "bun:test"
  2. import { createTestRenderer } from "@opentui/core/testing"
  3. import { Effect } from "effect"
  4. import { Global } from "@opencode-ai/core/global"
  5. import { createTuiResolvedConfig } from "./fixture/tui-runtime"
  6. import { createEventSource, createFetch, directory } from "./fixture/tui-sdk"
  7. test("SIGHUP clears title and disposes scoped resources once", async () => {
  8. const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
  9. const core = await import("@opentui/core")
  10. mock.module("@opentui/core", () => ({ ...core, createCliRenderer: async () => setup.renderer }))
  11. const titles: string[] = []
  12. const setTitle = setup.renderer.setTerminalTitle.bind(setup.renderer)
  13. setup.renderer.setTerminalTitle = (title) => {
  14. titles.push(title)
  15. setTitle(title)
  16. }
  17. const listeners = new Set(process.listeners("SIGHUP"))
  18. const events = createEventSource()
  19. const calls = createFetch()
  20. let started!: () => void
  21. const ready = new Promise<void>((resolve) => {
  22. started = resolve
  23. })
  24. let disposes = 0
  25. try {
  26. const { run } = await import("../src/app")
  27. const task = Effect.runPromise(
  28. run({
  29. url: "http://test",
  30. directory,
  31. config: createTuiResolvedConfig({ plugin_enabled: {} }),
  32. fetch: calls.fetch,
  33. events: events.source,
  34. args: {},
  35. pluginHost: {
  36. async start() {
  37. started()
  38. },
  39. async dispose() {
  40. disposes++
  41. },
  42. },
  43. }).pipe(Effect.provide(Global.defaultLayer)),
  44. )
  45. await ready
  46. process.emit("SIGHUP")
  47. await task
  48. expect(setup.renderer.isDestroyed).toBe(true)
  49. expect(titles.at(-1)).toBe("")
  50. expect(disposes).toBe(1)
  51. expect(process.listeners("SIGHUP").every((listener) => listeners.has(listener))).toBe(true)
  52. } finally {
  53. if (!setup.renderer.isDestroyed) setup.renderer.destroy()
  54. mock.restore()
  55. }
  56. })