/** @fileoverview Header plan-usage chip provider rows (Claude above Codex). */ import { readFileSync } from 'node:fs'; import { performance } from 'node:perf_hooks'; import { resolve } from 'node:path'; import vm from 'node:vm'; import { describe, expect, it, vi } from 'vitest'; function loadCodemanAppClass() { const constants = readFileSync(resolve(import.meta.dirname, '../src/web/public/constants.js'), 'utf8'); const source = readFileSync(resolve(import.meta.dirname, '../src/web/public/app.js'), 'utf8'); const chip = { innerHTML: '', title: '' }; const context = vm.createContext({ console, performance, setInterval: vi.fn(), clearInterval: vi.fn(), setTimeout, clearTimeout, requestAnimationFrame: vi.fn(), HTMLCanvasElement: class HTMLCanvasElement {}, fetch: (...args: Parameters) => global.fetch(...args), document: { addEventListener: vi.fn(), getElementById: (id: string) => (id === 'planUsageChip' ? chip : null), }, localStorage: { length: 0, key: vi.fn(), getItem: vi.fn(), setItem: vi.fn(), removeItem: vi.fn(), }, window: { addEventListener: vi.fn(), removeEventListener: vi.fn() }, MobileDetection: {}, }); vm.runInContext(`${constants}\n${source}\nglobalThis.__CodemanApp = CodemanApp;`, context); return { CodemanApp: (context as { __CodemanApp: new () => unknown }).__CodemanApp, chip, }; } type UsageApp = { updatePlanUsageChip: (data: unknown) => void }; describe('header plan usage chip', () => { it('renders Claude first and the main Codex limits underneath', () => { const { CodemanApp, chip } = loadCodemanAppClass(); const app = Object.create((CodemanApp as { prototype: object }).prototype) as UsageApp; app.updatePlanUsageChip({ fiveHour: { usedPercentage: 97, resetAt: 1000 }, sevenDay: { usedPercentage: 44, resetAt: 2000 }, codex: { sevenDay: { usedPercentage: 40, resetAt: 3000 } }, }); expect(chip.innerHTML).toContain('class="pu-row"'); expect(chip.innerHTML).toContain('class="pu-provider">Claude'); expect(chip.innerHTML).toContain('class="pu-provider">Codex'); expect(chip.innerHTML.indexOf('Claude')).toBeLessThan(chip.innerHTML.indexOf('Codex')); expect(chip.innerHTML).toContain('97%'); expect(chip.innerHTML).toContain('44%'); expect(chip.innerHTML).toContain('40%'); expect(chip.title).toContain('Claude plan usage'); expect(chip.title).toContain('Codex plan usage'); }); it('omits unavailable Codex windows instead of inventing zero usage', () => { const { CodemanApp, chip } = loadCodemanAppClass(); const app = Object.create((CodemanApp as { prototype: object }).prototype) as UsageApp; app.updatePlanUsageChip({ fiveHour: { usedPercentage: 10, resetAt: 1000 }, sevenDay: { usedPercentage: 20, resetAt: 2000 }, codex: { sevenDay: { usedPercentage: 40, resetAt: 3000 } }, }); const codexRow = chip.innerHTML.slice(chip.innerHTML.indexOf('Codex')); expect(codexRow).not.toContain('5h'); expect(codexRow).toContain('7d'); }); });