import { expect, mock, test } from "bun:test" import { createTestRenderer } from "@opentui/core/testing" import { Effect } from "effect" import { Global } from "@opencode-ai/core/global" import { createTuiResolvedConfig } from "./fixture/tui-runtime" import { createEventSource, createFetch, directory } from "./fixture/tui-sdk" test("SIGHUP clears title and disposes scoped resources once", async () => { const setup = await createTestRenderer({ width: 80, height: 24, useThread: false }) const core = await import("@opentui/core") mock.module("@opentui/core", () => ({ ...core, createCliRenderer: async () => setup.renderer })) const titles: string[] = [] const setTitle = setup.renderer.setTerminalTitle.bind(setup.renderer) setup.renderer.setTerminalTitle = (title) => { titles.push(title) setTitle(title) } const listeners = new Set(process.listeners("SIGHUP")) const events = createEventSource() const calls = createFetch() let started!: () => void const ready = new Promise((resolve) => { started = resolve }) let disposes = 0 try { const { run } = await import("../src/app") const task = Effect.runPromise( run({ url: "http://test", directory, config: createTuiResolvedConfig({ plugin_enabled: {} }), fetch: calls.fetch, events: events.source, args: {}, pluginHost: { async start() { started() }, async dispose() { disposes++ }, }, }).pipe(Effect.provide(Global.defaultLayer)), ) await ready process.emit("SIGHUP") await task expect(setup.renderer.isDestroyed).toBe(true) expect(titles.at(-1)).toBe("") expect(disposes).toBe(1) expect(process.listeners("SIGHUP").every((listener) => listeners.has(listener))).toBe(true) } finally { if (!setup.renderer.isDestroyed) setup.renderer.destroy() mock.restore() } })