Add CLI entry point with Commander.js, config management (~/.mcpctl/config.json with Zod validation), output formatters (table/json/yaml), config and status commands with dependency injection for testing. Fix sanitizeString regex ordering. 67 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { formatTable } from '../../src/formatters/table.js';
|
|
import type { Column } from '../../src/formatters/table.js';
|
|
|
|
interface TestRow {
|
|
name: string;
|
|
age: number;
|
|
city: string;
|
|
}
|
|
|
|
const columns: Column<TestRow>[] = [
|
|
{ header: 'NAME', key: 'name' },
|
|
{ header: 'AGE', key: 'age', align: 'right' },
|
|
{ header: 'CITY', key: 'city' },
|
|
];
|
|
|
|
describe('formatTable', () => {
|
|
it('returns empty message for no rows', () => {
|
|
expect(formatTable([], columns)).toBe('No results found.');
|
|
});
|
|
|
|
it('formats a single row', () => {
|
|
const rows = [{ name: 'Alice', age: 30, city: 'NYC' }];
|
|
const result = formatTable(rows, columns);
|
|
const lines = result.split('\n');
|
|
expect(lines).toHaveLength(3); // header, separator, data
|
|
expect(lines[0]).toContain('NAME');
|
|
expect(lines[0]).toContain('AGE');
|
|
expect(lines[0]).toContain('CITY');
|
|
expect(lines[2]).toContain('Alice');
|
|
expect(lines[2]).toContain('NYC');
|
|
});
|
|
|
|
it('right-aligns numeric columns', () => {
|
|
const rows = [{ name: 'Bob', age: 5, city: 'LA' }];
|
|
const result = formatTable(rows, columns);
|
|
const lines = result.split('\n');
|
|
// AGE column should be right-aligned: " 5" or "5" padded
|
|
const ageLine = lines[2];
|
|
// The age value should have leading space(s) for right alignment
|
|
expect(ageLine).toMatch(/\s+5/);
|
|
});
|
|
|
|
it('auto-sizes columns to content', () => {
|
|
const rows = [
|
|
{ name: 'A', age: 1, city: 'X' },
|
|
{ name: 'LongName', age: 100, city: 'LongCityName' },
|
|
];
|
|
const result = formatTable(rows, columns);
|
|
const lines = result.split('\n');
|
|
// Header should be at least as wide as longest data
|
|
expect(lines[0]).toContain('NAME');
|
|
expect(lines[2]).toContain('A');
|
|
expect(lines[3]).toContain('LongName');
|
|
expect(lines[3]).toContain('LongCityName');
|
|
});
|
|
|
|
it('truncates long values when width is fixed', () => {
|
|
const narrowCols: Column<TestRow>[] = [
|
|
{ header: 'NAME', key: 'name', width: 5 },
|
|
];
|
|
const rows = [{ name: 'VeryLongName', age: 0, city: '' }];
|
|
const result = formatTable(rows, narrowCols);
|
|
const lines = result.split('\n');
|
|
// Should be truncated with ellipsis
|
|
expect(lines[2].trim().length).toBeLessThanOrEqual(5);
|
|
expect(lines[2]).toContain('\u2026');
|
|
});
|
|
|
|
it('supports function-based column keys', () => {
|
|
const fnCols: Column<TestRow>[] = [
|
|
{ header: 'INFO', key: (row) => `${row.name} (${row.age})` },
|
|
];
|
|
const rows = [{ name: 'Eve', age: 25, city: 'SF' }];
|
|
const result = formatTable(rows, fnCols);
|
|
expect(result).toContain('Eve (25)');
|
|
});
|
|
|
|
it('handles separator line matching column widths', () => {
|
|
const rows = [{ name: 'Test', age: 1, city: 'Here' }];
|
|
const result = formatTable(rows, columns);
|
|
const lines = result.split('\n');
|
|
const separator = lines[1];
|
|
// Separator should consist of dashes and spaces
|
|
expect(separator).toMatch(/^[-\s]+$/);
|
|
});
|
|
});
|