import { describe, expect, it } from 'vitest'; import { applyArticlePatches, applyAndValidate } from '../articlePatches'; const article = () => ({ title: 'Onboarding', intro: 'Old intro.', sections: [ { heading: 'Day one', body: 'First day body, three sentences long. Welcome. Read the handbook.' }, { heading: 'Day two', body: 'Second day body. Three sentences. Meet your team.' }, ], keyTakeaways: ['Show up', 'Ask questions'], }); describe('applyArticlePatches', () => { it('does not mutate the input article', () => { const original = article(); const snapshot = JSON.parse(JSON.stringify(original)); applyArticlePatches(original, [ { name: 'set_intro', input: { intro: 'New intro.' } }, ]); expect(original).toEqual(snapshot); }); it('set_intro replaces the intro', () => { const result = applyArticlePatches(article(), [ { name: 'set_intro', input: { intro: 'Punchier intro.' } }, ]); expect(result.intro).toBe('Punchier intro.'); }); it('set_section replaces the matching section body (case-insensitive)', () => { const result = applyArticlePatches(article(), [ { name: 'set_section', input: { heading: 'DAY ONE', body: 'Rewritten body. With several sentences. Indeed.' } }, ]); expect(result.sections[0].body).toMatch(/Rewritten body/); expect(result.sections[1].body).toMatch(/Second day body/); }); it('add_section position=start prepends a new section', () => { const result = applyArticlePatches(article(), [ { name: 'add_section', input: { heading: 'Before', body: 'New intro section. Three sentences. Indeed.', position: 'start' } }, ]); expect(result.sections[0].heading).toBe('Before'); expect(result.sections).toHaveLength(3); }); it('add_section position=end appends a new section', () => { const result = applyArticlePatches(article(), [ { name: 'add_section', input: { heading: 'After', body: 'Closing section. Three sentences. Indeed.', position: 'end' } }, ]); expect(result.sections[2].heading).toBe('After'); }); it('remove_section drops the matching section', () => { const result = applyArticlePatches(article(), [ { name: 'remove_section', input: { heading: 'Day one' } }, ]); expect(result.sections).toHaveLength(1); expect(result.sections[0].heading).toBe('Day two'); }); it('replace_takeaways swaps the key takeaways', () => { const result = applyArticlePatches(article(), [ { name: 'replace_takeaways', input: { items: ['First', 'Second', 'Third'] } }, ]); expect(result.keyTakeaways).toEqual(['First', 'Second', 'Third']); }); it('applies multiple patches in order', () => { const result = applyArticlePatches(article(), [ { name: 'set_intro', input: { intro: 'Brand new intro.' } }, { name: 'remove_section', input: { heading: 'Day one' } }, { name: 'add_section', input: { heading: 'New', body: 'Body of the new section. Three sentences. Yes.', position: 'end' } }, ]); expect(result.intro).toBe('Brand new intro.'); expect(result.sections.map(s => s.heading)).toEqual(['Day two', 'New']); }); it('falls back to appending when set_section cannot find a matching heading', () => { const result = applyArticlePatches(article(), [ { name: 'set_section', input: { heading: 'Nonexistent', body: 'New body, with three sentences. Yes indeed. Foo.' } }, ]); expect(result.sections).toHaveLength(3); expect(result.sections[2].heading).toBe('Nonexistent'); }); }); describe('applyAndValidate', () => { it('returns the patched article when valid', () => { const patched = applyAndValidate(article(), [ { name: 'set_intro', input: { intro: 'Tighter intro.' } }, ]); expect(patched.intro).toBe('Tighter intro.'); }); it('throws when patches strip the article to invalid', () => { expect(() => applyAndValidate(article(), [ { name: 'remove_section', input: { heading: 'Day one' } }, { name: 'remove_section', input: { heading: 'Day two' } }, ]), ).toThrow(/invalid article/i); }); });