import { describe, it, expect } from 'vitest'; import { Formatter } from './formatter.js'; import { TerminalFormatter } from './terminal.formatter.js'; import { JsonFormatter } from './json.formatter.js'; import type { FeedItem } from '../../interfaces/feed.types.js'; describe('Formatter', () => { const formatter = new Formatter(); const createMockItem = (id: string, overrides: Partial = {}): FeedItem => ({ id, source: 'https://example.com/feed', title: 'Test Article', url: 'https://example.com/article/1', publishedAt: new Date('2026-05-05T10:30:00Z'), content: undefined, summary: undefined, ...overrides }); describe('format with terminal format', () => { it('should format empty array', async () => { const result = await formatter.format([], 'terminal'); expect(result).toContain('No items to display'); }); it('should format single item', async () => { const item = createMockItem('item-1', { title: 'My Article', source: 'https://myblog.com/feed' }); const result = await formatter.format([item], 'terminal'); expect(result).toContain('1.'); expect(result).toContain('My Article'); expect(result).toContain('https://myblog.com/feed'); expect(result).toContain('https://example.com/article/1'); }); it('should format multiple items', async () => { const items = [ createMockItem('item-1', { title: 'Article 1' }), createMockItem('item-2', { title: 'Article 2' }) ]; const result = await formatter.format(items, 'terminal'); expect(result).toContain('Found 2 items'); expect(result).toContain('1.'); expect(result).toContain('2.'); expect(result).toContain('Article 1'); expect(result).toContain('Article 2'); }); it('should include summary when present', async () => { const item = createMockItem('item-1', { summary: 'This is a test summary that might be truncated' }); const result = await formatter.format([item], 'terminal'); expect(result).toContain('This is a test summary'); }); }); describe('format with json format', () => { it('should format empty array', async () => { const result = await formatter.format([], 'json'); expect(JSON.parse(result)).toEqual([]); }); it('should format single item', async () => { const item = createMockItem('item-1', { title: 'My Article' }); const result = await formatter.format([item], 'json'); const parsed = JSON.parse(result); expect(parsed).toHaveLength(1); expect(parsed[0].id).toBe('item-1'); expect(parsed[0].title).toBe('My Article'); expect(parsed[0].publishedAt).toBe('2026-05-05T10:30:00.000Z'); }); it('should format multiple items', async () => { const items = [ createMockItem('item-1', { title: 'Article 1' }), createMockItem('item-2', { title: 'Article 2' }) ]; const result = await formatter.format(items, 'json'); const parsed = JSON.parse(result); expect(parsed).toHaveLength(2); expect(parsed[0].title).toBe('Article 1'); expect(parsed[1].title).toBe('Article 2'); }); it('should include optional content when present', async () => { const item = createMockItem('item-1', { content: 'Full article content here' }); const result = await formatter.format([item], 'json'); const parsed = JSON.parse(result); expect(parsed[0].content).toBe('Full article content here'); }); it('should include optional summary when present', async () => { const item = createMockItem('item-1', { summary: 'Article summary' }); const result = await formatter.format([item], 'json'); const parsed = JSON.parse(result); expect(parsed[0].summary).toBe('Article summary'); }); it('should not include undefined fields', async () => { const item = createMockItem('item-1'); const result = await formatter.format([item], 'json'); const parsed = JSON.parse(result); expect(parsed[0]).not.toHaveProperty('content'); expect(parsed[0]).not.toHaveProperty('summary'); }); it('should use ISO 8601 date format', async () => { const item = createMockItem('item-1', { publishedAt: new Date('2026-12-25T15:30:45.123Z') }); const result = await formatter.format([item], 'json'); const parsed = JSON.parse(result); expect(parsed[0].publishedAt).toBe('2026-12-25T15:30:45.123Z'); }); }); describe('error handling', () => { it('should throw FormatterError for HTML format', async () => { await expect(formatter.format([], 'html')).rejects.toMatchObject({ code: 'UNKNOWN', message: 'HTML format not implemented' }); }); it('should throw FormatterError for unknown format', async () => { await expect(formatter.format([], 'unknown-format' as any)).rejects.toMatchObject({ code: 'SERIALIZE_ERROR' }); }); }); }); describe('TerminalFormatter', () => { const terminalFormatter = new TerminalFormatter(); it('should truncate long summary text', () => { const longSummary = 'a'.repeat(100); const item: FeedItem = { id: 'item-1', source: 'https://example.com', title: 'Test', url: 'https://example.com/article', publishedAt: new Date(), summary: longSummary }; const result = terminalFormatter.format([item]); expect(result).toContain('...'); expect(result).not.toContain(longSummary); }); it('should format date in readable format', () => { const item: FeedItem = { id: 'item-1', source: 'https://example.com', title: 'Test', url: 'https://example.com/article', publishedAt: new Date('2026-05-05T10:30:00Z') }; const result = terminalFormatter.format([item]); expect(result).toContain('May'); expect(result).toContain('2026'); }); }); describe('JsonFormatter', () => { const jsonFormatter = new JsonFormatter(); it('should produce valid JSON', () => { const items: FeedItem[] = [ { id: 'test-1', source: 'https://example.com', title: 'Test Article', url: 'https://example.com/1', publishedAt: new Date() } ]; const result = jsonFormatter.format(items); expect(() => JSON.parse(result)).not.toThrow(); }); it('should convert Date to ISO string', () => { const date = new Date('2026-01-15T08:00:00Z'); const items: FeedItem[] = [ { id: 'test-1', source: 'https://example.com', title: 'Test', url: 'https://example.com/1', publishedAt: date } ]; const result = jsonFormatter.format(items); const parsed = JSON.parse(result); expect(parsed[0].publishedAt).toBe('2026-01-15T08:00:00.000Z'); }); });