/** * Fuel-Gauge Tests * Tests pure rendering functions without terminal interaction */ import { describe, expect, test } from 'bun:test'; import { FuelGauge } from './fuel-gauge'; describe('FuelGauge', () => { describe('buildProgressBar', () => { test('builds bar with gradient at position 0 moving right', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(0, 20, 1, false); // Gradient (4 chars) at position 0, moving right: ░▒▓█ expect(bar).toBe('░▒▓█················'); }); test('builds bar with gradient at position 0 moving left', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(0, 20, -1, false); // Gradient (4 chars) at position 0, moving left: █▓▒░ expect(bar).toBe('█▓▒░················'); }); test('builds bar with gradient at middle position moving right', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(5, 20, 1, false); // Gradient at position 5, moving right expect(bar).toBe('·····░▒▓█···········'); }); test('builds bar with gradient at middle position moving left', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(5, 20, -1, false); // Gradient at position 5, moving left expect(bar).toBe('·····█▓▒░···········'); }); test('builds bar with gradient at end position', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(16, 20, 1, false); // Gradient at position 16 (near right edge) expect(bar).toBe('················░▒▓█'); }); test('handles narrow bar width', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(0, 15, 1, false); expect(bar).toHaveLength(15); expect(bar).toMatch(/^[░▒▓█]+·+$/); }); test('handles wide bar width', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const bar = gauge.buildProgressBar(20, 100, 1, false); expect(bar).toHaveLength(100); expect(bar).toMatch(/^·+[░▒▓█]+·+$/); }); test('pulses front block when waiting (pulse=true, moving right)', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); // pulseState starts at 0, front block (right-most) alternates █ -> ▓ const bar1 = gauge.buildProgressBar(0, 20, 1, true); expect(bar1).toBe('░▒▓█················'); // pulseState % 2 = 0, shows solid █ // After pulseState increments to 1, front block shows next level // (Note: pulseState is private, we can't easily test the actual alternation) }); test('pulses front block when waiting (pulse=true, moving left)', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); // pulseState starts at 0, front block (left-most) alternates █ -> ▓ const bar1 = gauge.buildProgressBar(0, 20, -1, true); expect(bar1).toBe('█▓▒░················'); // pulseState % 2 = 0, shows solid █ }); }); describe('formatOutputLines', () => { test('returns last N lines', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const lines = ['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5']; const formatted = gauge.formatOutputLines(lines, 3); expect(formatted).toHaveLength(3); expect(formatted[0]).toContain('Line 3'); expect(formatted[1]).toContain('Line 4'); expect(formatted[2]).toContain('Line 5'); }); test('pads lines with indentation', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const lines = ['Test output']; const formatted = gauge.formatOutputLines(lines, 1); expect(formatted[0]).toBe(' Test output'); }); test('truncates long lines', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const longLine = 'a'.repeat(200); const formatted = gauge.formatOutputLines([longLine], 1); // Should be truncated with ... expect(formatted[0].length).toBeLessThan(200); expect(formatted[0]).toMatch(/\.\.\.$/); }); test('handles empty lines array', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const formatted = gauge.formatOutputLines([], 3); expect(formatted).toHaveLength(0); }); test('handles fewer lines than max', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); const lines = ['Line 1', 'Line 2']; const formatted = gauge.formatOutputLines(lines, 5); // Should return all available lines expect(formatted).toHaveLength(2); }); }); describe('addOutput and getOutput', () => { test('stores output lines', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); gauge.addOutput('Line 1'); gauge.addOutput('Line 2'); gauge.addOutput('Line 3'); const output = gauge.getOutput(); expect(output).toEqual(['Line 1', 'Line 2', 'Line 3']); }); test('strips ANSI codes from output', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); // Add line with ANSI color codes gauge.addOutput('\x1b[31mRed text\x1b[0m'); gauge.addOutput('\x1b[36mCyan text\x1b[0m'); const output = gauge.getOutput(); expect(output[0]).toBe('Red text'); expect(output[1]).toBe('Cyan text'); }); test('limits stored lines to 100', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); // Add 150 lines for (let i = 0; i < 150; i++) { gauge.addOutput(`Line ${i}`); } const output = gauge.getOutput(); expect(output).toHaveLength(100); // Should have last 100 lines (50-149) expect(output[0]).toBe('Line 50'); expect(output[99]).toBe('Line 149'); }); }); describe('start and stop', () => { test('start sets running state in test mode', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); gauge.start(); // Should not throw and should be testable expect(gauge).toBeDefined(); }); test('stop clears running state in test mode', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); gauge.start(); gauge.stop(true); // Should complete without error expect(gauge).toBeDefined(); }); test('handles stop before start', () => { const gauge = new FuelGauge('Test', { skipAnimation: true }); // Should not throw gauge.stop(true); expect(gauge).toBeDefined(); }); }); });