import { afterEach, describe, expect, test } from "bun:test"; import { act, useState } from "react"; import { emitKeypress as emitTuiKeypress, testRender, type TestKeyEvent } from "../../../renderers/opentui/test-utils"; import { appReducer, createInitialState, type AppAction, type AppState } from "../../../state/app/context"; import { createTestPaneConfig, TestPaneProvider } from "../../../test-support/pane"; import { createTestPluginRuntime } from "../../../test-support/plugin-runtime"; import type { PluginPersistence } from "../../../types/plugin"; import { attachTreasuryAuctionsPersistence, resetTreasuryAuctionsPersistence, } from "./cache"; import { TreasuryAuctionsPane } from "./pane"; import type { TreasuryAuction } from "./types"; function auction(overrides: Partial & { secType: string; securityTerm: string }): TreasuryAuction { return { id: `${overrides.secType}|${overrides.auctionDate ?? "2026-08-12"}|${overrides.securityTerm}`, auctionDate: "2026-08-12", highInvestmentRate: null, highYield: null, avgMedYield: null, highPrice: null, lowPrice: null, avgMedPrice: null, bidToCoverRatio: null, competitiveAccepted: null, indirectAccepted: null, primaryDealerAccepted: null, totalAccepted: null, offeringAmount: null, ...overrides, }; } const AUCTIONS: TreasuryAuction[] = [ auction({ secType: "Bond", securityTerm: "20-Year", auctionDate: "2026-08-19", offeringAmount: 16_000_000_000, }), auction({ secType: "Bill", securityTerm: "13-Week", auctionDate: "2026-08-17", highInvestmentRate: 3.802, bidToCoverRatio: 2.86, competitiveAccepted: 80_000_000_000, indirectAccepted: 48_000_000_000, totalAccepted: 98_725_863_900, }), auction({ secType: "Note", securityTerm: "10-Year", auctionDate: "2026-08-12", highYield: 4.683, bidToCoverRatio: 2.53, competitiveAccepted: 41_821_613_600, indirectAccepted: 32_087_936_000, primaryDealerAccepted: 3_597_810_000, totalAccepted: 52_623_557_100, }), ]; /** Fresh cache so the pane renders without touching the Treasury endpoint. */ function seedCache(): void { const record = { value: AUCTIONS, fetchedAt: Date.now(), stale: false, staleAt: Date.now() + 60_000, expiresAt: Date.now() + 60_000 }; attachTreasuryAuctionsPersistence({ getResource: () => record, setResource: () => record, } as unknown as PluginPersistence); } let testSetup: Awaited> | undefined; afterEach(async () => { resetTreasuryAuctionsPersistence(); if (!testSetup) return; await act(async () => { testSetup!.renderer.destroy(); }); testSetup = undefined; }); const PANE_INSTANCE_ID = "treasury-auctions:test"; const paneRuntime = createTestPluginRuntime(); function Harness() { // The filter tab and the open auction are pane state, so the harness needs a // reducer; filtering dispatches both in the same batch. const [paneState, setPaneState] = useState({}); const state = createInitialState(createTestPaneConfig("/tmp/gloomberb-auctions-pane-test", { paneId: "treasury-auctions", instanceId: PANE_INSTANCE_ID, })); state.paneState = paneState; const dispatch = (action: AppAction) => setPaneState( (current) => appReducer({ ...state, paneState: current }, action).paneState, ); return ( ); } async function renderSettled() { await act(async () => { await testSetup!.renderOnce(); await testSetup!.renderOnce(); }); } const emitKeypress = (event: TestKeyEvent) => emitTuiKeypress(testSetup!, event); describe("TreasuryAuctionsPane", () => { test("renders auction metrics and keeps one placeholder for unpublished results", async () => { seedCache(); testSetup = await testRender(, { width: 92, height: 20 }); await renderSettled(); const frame = testSetup.captureCharFrame(); expect(frame).toContain("13-Week"); expect(frame).toContain("3.802%"); expect(frame).toContain("2.86"); // Indirect share is derived, not reported. expect(frame).toContain("60.0%"); // The 20-Year is announced but unpublished: every metric cell reads the // same placeholder the reported-but-missing cells use. expect(frame).toContain("20-Year"); expect(frame).not.toContain("pending"); }); test("opens a detail view for the selected auction", async () => { seedCache(); testSetup = await testRender(, { width: 92, height: 20 }); await renderSettled(); await emitKeypress({ name: "down", sequence: "\u001B[B" }); await emitKeypress({ name: "enter", sequence: "\r" }); await renderSettled(); const detail = testSetup.captureCharFrame(); expect(detail).toContain("Bill 13-Week"); expect(detail).toContain("Bid-to-cover"); expect(detail).toContain("Total accepted"); }); test("the filter key narrows the board to one security group", async () => { seedCache(); testSetup = await testRender(, { width: 92, height: 20 }); await renderSettled(); // all -> bills await emitKeypress({ name: "f", sequence: "f" }); await renderSettled(); const bills = testSetup.captureCharFrame(); expect(bills).toContain("13-Week"); expect(bills).not.toContain("10-Year"); }); });