import { describe, expect, test } from 'bun:test'; import { extractChapterNumber, latestChapter, parseFeed } from 'packages/obsidian/src/library/rss'; const rssSample = ` Chainsaw Man Updates Chainsaw Man Chapter 214 Thu, 30 Jul 2026 12:00:00 GMT https://example.com/csm-214 Chainsaw Man Chapter 213.5 Thu, 23 Jul 2026 12:00:00 GMT https://example.com/csm-213-5 Chainsaw Man Chapter 213 Thu, 16 Jul 2026 12:00:00 GMT https://example.com/csm-213 `; const atomSample = ` Some Manga Feed Ch. 12 — name 2026-08-01T09:30:00Z https://example.com/entry/12 `; const cdataSample = ` <![CDATA[One Piece Chapter 1120]]> Mon, 01 Jun 2026 00:00:00 GMT id-cdata `; const entitySample = ` Attack & Titan 'Special' Chapter 5 Mon, 01 Jun 2026 00:00:00 GMT id-entity `; const numberlessSample = ` Chainsaw Man - Extra Announcement Thu, 30 Jul 2026 12:00:00 GMT a Chainsaw Man - Fan Art Contest Thu, 16 Jul 2026 12:00:00 GMT b `; describe('parseFeed', () => { test('RSS 2.0 — 3 items, title/date/id mapped, newest first as-authored', () => { const items = parseFeed(rssSample); expect(items).toEqual([ { title: 'Chainsaw Man Chapter 214', date: '2026-07-30', id: 'https://example.com/csm-214' }, { title: 'Chainsaw Man Chapter 213.5', date: '2026-07-23', id: 'https://example.com/csm-213-5' }, { title: 'Chainsaw Man Chapter 213', date: '2026-07-16', id: 'https://example.com/csm-213' }, ]); }); test('Atom — entry title/updated/id mapped', () => { const items = parseFeed(atomSample); expect(items).toEqual([{ title: 'Ch. 12 — name', date: '2026-08-01', id: 'https://example.com/entry/12' }]); }); test('CDATA title unwrapped', () => { const items = parseFeed(cdataSample); expect(items[0].title).toBe('One Piece Chapter 1120'); }); test('entity-encoded title decoded', () => { const items = parseFeed(entitySample); expect(items[0].title).toBe(`Attack & Titan 'Special' Chapter 5`); }); test('empty xml → []', () => { expect(parseFeed('')).toEqual([]); }); test('garbage xml → []', () => { expect(parseFeed('not xml at all, just some random text')).toEqual([]); }); }); describe('extractChapterNumber', () => { test('"Chapter 214" → 214', () => { expect(extractChapterNumber('Chapter 214')).toBe(214); }); test('"ch.213.5" → 213.5', () => { expect(extractChapterNumber('ch.213.5')).toBe(213.5); }); test('"#77" → 77', () => { expect(extractChapterNumber('#77')).toBe(77); }); test('"Episode 5" → null', () => { expect(extractChapterNumber('Episode 5')).toBeNull(); }); test('"Vol. 3 Chapter 21" → 21', () => { expect(extractChapterNumber('Vol. 3 Chapter 21')).toBe(21); }); test('numberless title → null', () => { expect(extractChapterNumber('Chainsaw Man - Extra Announcement')).toBeNull(); }); }); describe('latestChapter', () => { test('empty items → null', () => { expect(latestChapter([])).toBeNull(); }); test('RSS sample → highest chapter (214) wins', () => { const result = latestChapter(parseFeed(rssSample)); expect(result).toEqual({ chapter: 214, date: '2026-07-30', title: 'Chainsaw Man Chapter 214' }); }); test('Atom sample → single entry chapter 12', () => { const result = latestChapter(parseFeed(atomSample)); expect(result).toEqual({ chapter: 12, date: '2026-08-01', title: 'Ch. 12 — name' }); }); test('numberless titles → date-based fallback, chapter null', () => { const result = latestChapter(parseFeed(numberlessSample)); expect(result).toEqual({ chapter: null, date: '2026-07-30', title: 'Chainsaw Man - Extra Announcement' }); }); });