import { expect, test } from "bun:test"; import type { Plugin } from "@opencode/plugin/tui"; import { testRender, type JSX } from "@opentui/solid"; import { createSignal } from "solid-js"; import { build } from "../scripts/build.js"; import type { QuotaStatus } from "../src/quota.js"; type SlotRender = (input: { readonly sessionID: string }) => JSX.Element; const status = (plan: string, remaining: number): QuotaStatus => ({ plan, fetchedAt: 1, windows: [ { id: "codex:primary", label: null, seconds: 18_000, used: 100 - remaining, resetsAt: 2, remaining, sessionConsumed: 1.5, }, ], }); test("built quota slot stays reactive across pending, empty, failure, update, and session states", async () => { await build(); const { default: plugin } = await import("../dist/tui.js"); const pending = Promise.withResolvers(); const responses: Array<() => Promise> = [() => pending.promise]; const sessions: string[] = []; let slotRender: SlotRender | undefined; let notifyUpdated: (() => void) | undefined; const quota = { status(input: { sessionID: string }) { sessions.push(input.sessionID); const response = responses.shift(); if (!response) throw new Error("Missing test response"); return response(); }, events: { on(_event: "updated", callback: () => void) { notifyUpdated = callback; return () => undefined; }, }, }; const contextFixture = { client: { rpc: () => quota }, ui: { slot(claim: { render: SlotRender }) { slotRender = claim.render; return () => undefined; }, }, theme: { text: { default: "#ffffff", muted: "#888888" } }, }; // SAFETY: The slot setup only reads the client RPC, slot registration, and theme // members implemented by this focused rendering fixture. const context = Object.assign({} as Plugin.Context, contextFixture); const cleanup = await plugin.setup(context); if (!slotRender || !notifyUpdated) throw new Error("Plugin did not register its slot"); const [sessionID, setSessionID] = createSignal("session-one"); const renderSlot = slotRender; const setup = await testRender( () => renderSlot({ get sessionID() { return sessionID(); }, }), { width: 70, height: 5 }, ); try { await setup.renderOnce(); expect(setup.captureCharFrame()).not.toContain("ChatGPT quota"); pending.resolve(status("plus", 75)); await setup.waitForFrame((frame) => frame.includes("ChatGPT quota (plus)")); expect(setup.captureCharFrame()).toContain("5h: 75% left ยท ~1.5pp this session"); responses.push(async () => null); notifyUpdated(); await setup.waitForFrame((frame) => !frame.includes("ChatGPT quota")); responses.push(async () => { throw new Error("disconnected"); }); notifyUpdated(); await setup.waitFor(() => sessions.length === 3); expect(setup.captureCharFrame()).not.toContain("ChatGPT quota"); responses.push(async () => status("pro", 42)); setSessionID("session-two"); await setup.waitForFrame((frame) => frame.includes("ChatGPT quota (pro)")); expect(sessions).toEqual(["session-one", "session-one", "session-one", "session-two"]); } finally { setup.renderer.destroy(); if (cleanup) await cleanup(); } });