import { describe, it, expect } from 'vitest'; import { AtomParser } from './atom.parser.js'; describe('AtomParser', () => { const parser = new AtomParser(); describe('parse', () => { it('parses valid Atom feed with all fields', async () => { const xml = ` Test Article Full content

]]>
2024-09-06T09:00:00Z
`; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items).toHaveLength(1); expect(items[0].title).toBe('Test Article'); expect(items[0].url).toBe('https://example.com/article'); expect(items[0].summary).toBe('This is a summary'); expect(items[0].content).toBe('

Full content

'); expect(items[0].publishedAt).toEqual(new Date('2024-09-06T09:00:00Z')); expect(items[0].source).toBe('https://example.com/feed.xml'); expect(items[0].id).toBeDefined(); }); it('parses Atom with only required fields', async () => { const xml = ` Minimal Article `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items).toHaveLength(1); expect(items[0].title).toBe('Minimal Article'); expect(items[0].url).toBe('https://example.com/minimal'); expect(items[0].summary).toBeUndefined(); expect(items[0].content).toBeUndefined(); expect(items[0].publishedAt).toBeInstanceOf(Date); }); it('parses multiple entries', async () => { const xml = ` Article 1 2024-09-06T09:00:00Z Article 2 2024-09-07T10:00:00Z `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items).toHaveLength(2); expect(items[0].title).toBe('Article 1'); expect(items[1].title).toBe('Article 2'); }); it('returns empty array when no entries', async () => { const xml = ` Empty Feed `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items).toHaveLength(0); }); it('prefers rel="alternate" link', async () => { const xml = ` Test `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items[0].url).toBe('https://example.com/article'); }); it('falls back to first non-self link', async () => { const xml = ` Test `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items[0].url).toBe('https://example.com/article'); }); it('throws on missing title', async () => { const xml = ` `; await expect(parser.parse(xml, 'https://example.com/feed.xml')).rejects.toThrow( 'missing required field: title' ); }); it('throws on missing link with href', async () => { const xml = ` Article Without Link `; await expect(parser.parse(xml, 'https://example.com/feed.xml')).rejects.toThrow( 'missing required field: link with href' ); }); it('throws on invalid XML', async () => { const xml = 'not xml at all'; await expect(parser.parse(xml, 'https://example.com/feed.xml')).rejects.toThrow( 'Invalid XML' ); }); it('throws on missing feed root element', async () => { const xml = ''; await expect(parser.parse(xml, 'https://example.com/feed.xml')).rejects.toThrow( 'missing root element' ); }); it('uses when is missing', async () => { const xml = ` Test 2024-09-06T09:00:00Z `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items[0].publishedAt).toEqual(new Date('2024-09-06T09:00:00Z')); }); it('prefers over ', async () => { const xml = ` Test 2024-09-06T09:00:00Z 2024-09-07T10:00:00Z `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items[0].publishedAt).toEqual(new Date('2024-09-06T09:00:00Z')); }); it('generates deterministic IDs', async () => { const xml = ` Test 2024-09-06T09:00:00Z `; const items1 = await parser.parse(xml, 'https://example.com/feed.xml'); const items2 = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items1[0].id).toBe(items2[0].id); }); it('handles multiple links in array format', async () => { const xml = ` Test `; const items = await parser.parse(xml, 'https://example.com/feed.xml'); expect(items[0].url).toBe('https://example.com/article'); }); }); describe('supports', () => { it('returns true for application/atom+xml', () => { expect(parser.supports('application/atom+xml')).toBe(true); }); it('returns true for atom in content type', () => { expect(parser.supports('application/atom')).toBe(true); }); it('returns false for rss content type', () => { expect(parser.supports('application/rss+xml')).toBe(false); }); it('is case insensitive', () => { expect(parser.supports('APPLICATION/ATOM+XML')).toBe(true); }); }); });