Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

560 lines
18 KiB
TypeScript

/**
* OutputFormatter Tests
*
* Tests the output module in isolation, covering:
* - Color methods (bold, dim, success, error, warning, info, highlight)
* - Color enable/disable
* - Verbosity levels (quiet, normal, verbose, debug)
* - Formatted output: printSuccess, printError, printWarning, printInfo, printDebug, printTrace
* - Table formatting: borders, no borders, alignment, column width, maxWidth, formatter
* - Progress bar rendering
* - JSON output (pretty and compact)
* - List and numbered list output
* - Box output with and without title
* - supportsColor environment detection
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { OutputFormatter } from '../src/output.js';
describe('OutputFormatter', () => {
let stdoutWrite: ReturnType<typeof vi.spyOn>;
let stderrWrite: ReturnType<typeof vi.spyOn>;
let captured: string[];
let errorCaptured: string[];
beforeEach(() => {
captured = [];
errorCaptured = [];
stdoutWrite = vi.spyOn(process.stdout, 'write').mockImplementation((s: string | Uint8Array) => {
captured.push(String(s));
return true;
});
stderrWrite = vi.spyOn(process.stderr, 'write').mockImplementation((s: string | Uint8Array) => {
errorCaptured.push(String(s));
return true;
});
});
afterEach(() => {
vi.restoreAllMocks();
delete process.env.NO_COLOR;
delete process.env.FORCE_COLOR;
});
// -------------------------------------------------------------------------
// Color enable/disable
// -------------------------------------------------------------------------
describe('color enable/disable', () => {
it('should disable colors via setColorEnabled(false)', () => {
const out = new OutputFormatter({ color: true });
out.setColorEnabled(false);
expect(out.isColorEnabled()).toBe(false);
// With color disabled, color methods return plain text
expect(out.bold('hello')).toBe('hello');
});
it('should enable colors via setColorEnabled(true)', () => {
const out = new OutputFormatter({ color: false });
out.setColorEnabled(true);
expect(out.isColorEnabled()).toBe(true);
expect(out.bold('hello')).toContain('\x1b[');
});
it('should respect color=false in constructor', () => {
const out = new OutputFormatter({ color: false });
expect(out.isColorEnabled()).toBe(false);
expect(out.success('ok')).toBe('ok');
});
it('should respect color=true in constructor', () => {
const out = new OutputFormatter({ color: true });
expect(out.isColorEnabled()).toBe(true);
expect(out.error('fail')).toContain('\x1b[31m');
});
});
// -------------------------------------------------------------------------
// Color methods
// -------------------------------------------------------------------------
describe('color methods', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: true });
});
it('bold should wrap text with bold ANSI code', () => {
const result = out.bold('test');
expect(result).toContain('\x1b[1m');
expect(result).toContain('test');
expect(result).toContain('\x1b[0m');
});
it('dim should wrap text with dim ANSI code', () => {
const result = out.dim('test');
expect(result).toContain('\x1b[2m');
});
it('success should wrap text in green', () => {
const result = out.success('ok');
expect(result).toContain('\x1b[32m');
});
it('error should wrap text in red', () => {
const result = out.error('fail');
expect(result).toContain('\x1b[31m');
});
it('warning should wrap text in yellow', () => {
const result = out.warning('careful');
expect(result).toContain('\x1b[33m');
});
it('info should wrap text in blue', () => {
const result = out.info('note');
expect(result).toContain('\x1b[34m');
});
it('highlight should wrap text in cyan+bold', () => {
const result = out.highlight('important');
expect(result).toContain('\x1b[36m');
expect(result).toContain('\x1b[1m');
});
it('color with multiple colors should apply all codes', () => {
const result = out.color('text', 'red', 'bold');
expect(result).toContain('\x1b[31m');
expect(result).toContain('\x1b[1m');
});
});
// -------------------------------------------------------------------------
// Verbosity
// -------------------------------------------------------------------------
describe('verbosity', () => {
it('default verbosity should be "normal"', () => {
const out = new OutputFormatter();
expect(out.getVerbosity()).toBe('normal');
});
it('should set verbosity via constructor', () => {
const out = new OutputFormatter({ verbosity: 'debug' });
expect(out.getVerbosity()).toBe('debug');
});
it('should update verbosity via setVerbosity', () => {
const out = new OutputFormatter();
out.setVerbosity('quiet');
expect(out.getVerbosity()).toBe('quiet');
expect(out.isQuiet()).toBe(true);
});
it('isVerbose should be true for "verbose" and "debug"', () => {
const out = new OutputFormatter();
out.setVerbosity('verbose');
expect(out.isVerbose()).toBe(true);
out.setVerbosity('debug');
expect(out.isVerbose()).toBe(true);
out.setVerbosity('normal');
expect(out.isVerbose()).toBe(false);
});
it('isDebug should only be true for "debug"', () => {
const out = new OutputFormatter();
out.setVerbosity('debug');
expect(out.isDebug()).toBe(true);
out.setVerbosity('verbose');
expect(out.isDebug()).toBe(false);
});
});
// -------------------------------------------------------------------------
// Formatted output respecting verbosity
// -------------------------------------------------------------------------
describe('formatted output', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('printSuccess should always output', () => {
out.setVerbosity('quiet');
out.printSuccess('done');
expect(captured.join('')).toContain('done');
});
it('printError should always output to stderr', () => {
out.setVerbosity('quiet');
out.printError('bad', 'details here');
const allErr = errorCaptured.join('');
expect(allErr).toContain('bad');
expect(allErr).toContain('details here');
});
it('printWarning should be suppressed in quiet mode', () => {
out.setVerbosity('quiet');
out.printWarning('warning');
expect(captured.join('')).not.toContain('warning');
});
it('printWarning should output in normal mode', () => {
out.setVerbosity('normal');
out.printWarning('warning');
expect(captured.join('')).toContain('warning');
});
it('printInfo should be suppressed in quiet mode', () => {
out.setVerbosity('quiet');
out.printInfo('information');
expect(captured.join('')).not.toContain('information');
});
it('printInfo should output in normal mode', () => {
out.setVerbosity('normal');
out.printInfo('information');
expect(captured.join('')).toContain('information');
});
it('printDebug should only output in verbose/debug mode', () => {
out.setVerbosity('normal');
out.printDebug('debug msg');
expect(captured.join('')).not.toContain('debug msg');
out.setVerbosity('verbose');
out.printDebug('debug msg');
expect(captured.join('')).toContain('debug msg');
});
it('printTrace should only output in debug mode', () => {
out.setVerbosity('verbose');
out.printTrace('trace msg');
expect(captured.join('')).not.toContain('trace msg');
out.setVerbosity('debug');
out.printTrace('trace msg');
expect(captured.join('')).toContain('trace msg');
});
});
// -------------------------------------------------------------------------
// write/writeln/writeError/writeErrorln
// -------------------------------------------------------------------------
describe('write methods', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('write should write to stdout without newline', () => {
out.write('hello');
expect(captured).toEqual(['hello']);
});
it('writeln should write to stdout with newline', () => {
out.writeln('hello');
expect(captured).toEqual(['hello\n']);
});
it('writeln with no argument should write empty newline', () => {
out.writeln();
expect(captured).toEqual(['\n']);
});
it('writeError should write to stderr', () => {
out.writeError('err');
expect(errorCaptured).toEqual(['err']);
});
it('writeErrorln should write to stderr with newline', () => {
out.writeErrorln('err');
expect(errorCaptured).toEqual(['err\n']);
});
});
// -------------------------------------------------------------------------
// Table formatting
// -------------------------------------------------------------------------
describe('table formatting', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('should render a basic table with borders and header', () => {
const result = out.table({
columns: [
{ key: 'name', header: 'Name' },
{ key: 'value', header: 'Value' },
],
data: [
{ name: 'alpha', value: '100' },
{ name: 'beta', value: '200' },
],
});
expect(result).toContain('Name');
expect(result).toContain('Value');
expect(result).toContain('alpha');
expect(result).toContain('beta');
expect(result).toContain('+'); // border character
expect(result).toContain('|'); // vertical border
});
it('should render table without borders', () => {
const result = out.table({
columns: [{ key: 'x', header: 'X' }],
data: [{ x: '1' }],
border: false,
});
expect(result).not.toContain('+');
expect(result).toContain('X');
expect(result).toContain('1');
});
it('should render table without header', () => {
const result = out.table({
columns: [{ key: 'x', header: 'X' }],
data: [{ x: 'val' }],
header: false,
});
expect(result).toContain('val');
// The header 'X' should not appear as a data row
const lines = result.split('\n');
const dataLines = lines.filter(l => l.includes('val'));
expect(dataLines.length).toBeGreaterThan(0);
});
it('should use column formatter', () => {
const result = out.table({
columns: [
{
key: 'count',
header: 'Count',
format: (v: unknown) => `#${v}`,
},
],
data: [{ count: 42 }],
});
expect(result).toContain('#42');
});
it('should handle null/undefined values gracefully', () => {
const result = out.table({
columns: [{ key: 'x', header: 'X' }],
data: [{ x: null }, { x: undefined }],
});
expect(result).toBeDefined();
// Should not throw and should contain empty-ish cells
});
it('should handle right alignment', () => {
const result = out.table({
columns: [{ key: 'num', header: 'Num', align: 'right' as const }],
data: [{ num: '5' }],
});
expect(result).toContain('5');
});
it('should handle center alignment', () => {
const result = out.table({
columns: [{ key: 'mid', header: 'Mid', align: 'center' as const }],
data: [{ mid: 'X' }],
});
expect(result).toContain('X');
});
it('should respect column width limit', () => {
const result = out.table({
columns: [{ key: 'long', header: 'L', width: 5 }],
data: [{ long: 'abcdefghij' }],
});
// Content should be truncated
expect(result).toContain('...');
});
it('should respect maxWidth constraint', () => {
const result = out.table({
columns: [
{ key: 'a', header: 'A' },
{ key: 'b', header: 'B' },
],
data: [{ a: 'aaaaaaaaaa', b: 'bbbbbbbbbb' }],
maxWidth: 20,
});
expect(result).toBeDefined();
});
it('printTable should write table to stdout', () => {
out.printTable({
columns: [{ key: 'x', header: 'X' }],
data: [{ x: '1' }],
});
expect(captured.join('')).toContain('X');
});
});
// -------------------------------------------------------------------------
// Progress bar
// -------------------------------------------------------------------------
describe('progress bar', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('should render progress bar at 0%', () => {
const bar = out.progressBar(0, 100);
expect(bar).toContain('0.0%');
});
it('should render progress bar at 50%', () => {
const bar = out.progressBar(50, 100);
expect(bar).toContain('50.0%');
});
it('should render progress bar at 100%', () => {
const bar = out.progressBar(100, 100);
expect(bar).toContain('100.0%');
});
it('should clamp progress bar above 100%', () => {
const bar = out.progressBar(200, 100);
expect(bar).toContain('100.0%');
});
it('should respect custom width', () => {
const bar = out.progressBar(50, 100, 20);
expect(bar).toContain('50.0%');
});
});
// -------------------------------------------------------------------------
// JSON output
// -------------------------------------------------------------------------
describe('JSON output', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('should produce pretty JSON by default', () => {
const result = out.json({ a: 1 });
expect(result).toContain(' "a": 1');
});
it('should produce compact JSON when pretty=false', () => {
const result = out.json({ a: 1 }, false);
expect(result).toBe('{"a":1}');
});
it('printJson should write JSON to stdout', () => {
out.printJson({ key: 'val' });
expect(captured.join('')).toContain('"key": "val"');
});
});
// -------------------------------------------------------------------------
// List output
// -------------------------------------------------------------------------
describe('list output', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('should render bulleted list', () => {
const result = out.list(['apple', 'banana']);
expect(result).toContain('- apple');
expect(result).toContain('- banana');
});
it('should use custom bullet', () => {
const result = out.list(['item'], '*');
expect(result).toContain('* item');
});
it('printList should write to stdout', () => {
out.printList(['x', 'y']);
const text = captured.join('');
expect(text).toContain('- x');
expect(text).toContain('- y');
});
it('should render numbered list', () => {
const result = out.numberedList(['first', 'second', 'third']);
expect(result).toContain('1. first');
expect(result).toContain('2. second');
expect(result).toContain('3. third');
});
it('printNumberedList should write to stdout', () => {
out.printNumberedList(['a', 'b']);
const text = captured.join('');
expect(text).toContain('1. a');
expect(text).toContain('2. b');
});
});
// -------------------------------------------------------------------------
// Box output
// -------------------------------------------------------------------------
describe('box output', () => {
let out: OutputFormatter;
beforeEach(() => {
out = new OutputFormatter({ color: false });
});
it('should render box without title', () => {
const result = out.box('Hello world');
expect(result).toContain('+');
expect(result).toContain('|');
expect(result).toContain('Hello world');
});
it('should render box with title', () => {
const result = out.box('Content', 'Title');
expect(result).toContain('Title');
expect(result).toContain('Content');
expect(result).toContain('+');
});
it('should handle multiline content', () => {
const result = out.box('Line 1\nLine 2\nLine 3');
expect(result).toContain('Line 1');
expect(result).toContain('Line 2');
expect(result).toContain('Line 3');
});
it('printBox should write to stdout', () => {
out.printBox('test content', 'box title');
const text = captured.join('');
expect(text).toContain('test content');
expect(text).toContain('box title');
});
});
// -------------------------------------------------------------------------
// supportsColor environment detection
// -------------------------------------------------------------------------
describe('supportsColor environment detection', () => {
it('should disable color when NO_COLOR is set', () => {
process.env.NO_COLOR = '1';
const out = new OutputFormatter();
expect(out.isColorEnabled()).toBe(false);
});
it('should enable color when FORCE_COLOR is set', () => {
process.env.FORCE_COLOR = '1';
const out = new OutputFormatter();
expect(out.isColorEnabled()).toBe(true);
});
});
});