import { describe, it, expect } from 'vitest'; import { buildPublicationAsciiDoc, renderPublication, renderPublicationFromEvents, renderEvent, toPublicationIndexLike, } from './publication-helper.js'; const index = { kind: 30040 as const, title: 'My Book', d: 'my-book-v1', a: ['30041:abc:ch1', '30041:abc:ch2'], type: 'book', }; const sections = [ { address: '30041:abc:ch1', title: 'Chapter One', content: 'First chapter body.' }, { address: '30041:abc:ch2', title: 'Chapter Two', content: 'Second chapter body.' }, ]; describe('buildPublicationAsciiDoc', () => { it('builds combined AsciiDoc from index and sections', () => { const adoc = buildPublicationAsciiDoc({ index, sections }); expect(adoc).toContain('= My Book'); expect(adoc).toContain('== Chapter One'); expect(adoc).toContain('First chapter body.'); expect(adoc).toContain('== Chapter Two'); expect(adoc).toContain('Second chapter body.'); }); it('throws if kind is not 30040', () => { expect(() => buildPublicationAsciiDoc({ index: { ...index, kind: 1 as 30040 }, sections, }) ).toThrow('Expected kind 30040'); }); }); describe('renderPublication', () => { it('returns asciidoc, html, and tableOfContents', () => { const out = renderPublication({ index, sections }); expect(out.asciidoc).toContain('= My Book'); expect(out.html).toContain('My Book'); expect(out.html).toContain('Chapter One'); expect(out.html).toContain('First chapter body.'); expect(typeof out.tableOfContents).toBe('string'); }); it('can disable toc', () => { const out = renderPublication({ index, sections }, { toc: false }); expect(out.tableOfContents).toBe(''); }); }); describe('toPublicationIndexLike', () => { it('parses event-like payload with kind 30040 and title', () => { const payload = { kind: 30040, tags: [ ['title', 'Test Pub'], ['d', 'test-d'], ['a', '30041:pk1:d1'], ['a', '30041:pk1:d2'], ['type', 'blog'], ['image', 'https://example.com/cover.png'], ], }; const parsed = toPublicationIndexLike(payload); expect(parsed).not.toBeNull(); expect(parsed!.kind).toBe(30040); expect(parsed!.title).toBe('Test Pub'); expect(parsed!.d).toBe('test-d'); expect(parsed!.a).toEqual(['30041:pk1:d1', '30041:pk1:d2']); expect(parsed!.type).toBe('blog'); expect(parsed!.image).toBe('https://example.com/cover.png'); }); it('returns null for wrong kind', () => { expect(toPublicationIndexLike({ kind: 1, tags: [['title', 'X']] })).toBeNull(); }); it('returns null when title is missing', () => { expect(toPublicationIndexLike({ kind: 30040, tags: [['d', 'x']] })).toBeNull(); }); }); describe('renderEvent', () => { it('accepts event JSON and returns HTML', () => { const event = { kind: 30041, content: '= One section\n\nSome *bold* text.', tags: [['title', 'One section']], pubkey: 'abc', }; const html = renderEvent(event); expect(html).toContain('bold'); }); it('accepts 30818 wiki event', () => { const event = { kind: 30818, content: 'Wiki *content*.', tags: [] }; const html = renderEvent(event); expect(html).toContain('Wiki'); expect(html).toContain('content'); }); }); describe('renderPublicationFromEvents', () => { it('accepts index event + section events JSON and returns one HTML page', () => { const indexEvent = { kind: 30040, content: '', tags: [ ['title', 'My Book'], ['d', 'my-book'], ['a', '30041:pk:ch1'], ['a', '30041:pk:ch2'], ], pubkey: 'pk', }; const sectionEvents = [ { kind: 30041, content: 'First chapter body.', tags: [['title', 'Chapter One'], ['d', 'ch1']], pubkey: 'pk', }, { kind: 30041, content: 'Second chapter body.', tags: [['title', 'Chapter Two'], ['d', 'ch2']], pubkey: 'pk', }, ]; const out = renderPublicationFromEvents(indexEvent, sectionEvents); expect(out.html).toContain('My Book'); expect(out.html).toContain('Chapter One'); expect(out.html).toContain('First chapter body.'); expect(out.html).toContain('Chapter Two'); expect(out.html).toContain('Second chapter body.'); expect(out.asciidoc).toContain('= My Book'); }); it('throws if index event is not kind 30040', () => { expect(() => renderPublicationFromEvents( { kind: 1, content: '', tags: [['title', 'X']] }, [] ) ).toThrow('Event kind must be 30040'); }); });