import { describe, it, expect } from 'vitest'; import { renderTemplate, MissingTemplateVariableError } from './template.renderer'; describe('renderTemplate (pure)', () => { it('substitutes variables into subject / html / text', () => { const out = renderTemplate( { subject: 'Receipt for {{firstName}}', bodyHtml: '
Hi {{firstName}}
', bodyText: 'Hi {{firstName}}', variables: ['firstName'] }, { firstName: 'Dana' }, ); expect(out.subject).toBe('Receipt for Dana'); expect(out.html).toBe('Hi Dana
'); expect(out.text).toBe('Hi Dana'); }); // The load-bearing safety property: a customer-supplied name goes into an HTML email body. it('escapes HTML in the html body, but leaves subject/text verbatim (XSS)', () => { const out = renderTemplate( { subject: 'Hi {{firstName}}', bodyHtml: '{{firstName}}
', bodyText: '{{firstName}}', variables: ['firstName'] }, { firstName: '' }, ); expect(out.html).toBe('<script>alert(1)</script>
'); expect(out.html).not.toContain(''); // plaintext is not HTML → not escaped }); it('supports {{#if}} and {{#each}}', () => { const out = renderTemplate( { bodyText: '{{#if companyName}}Company: {{companyName}}\n{{/if}}{{#each items}}- {{this}}\n{{/each}}', variables: [] }, { companyName: 'Acme', items: ['a', 'b'] }, ); expect(out.text).toBe('Company: Acme\n- a\n- b\n'); }); it('throws when a declared variable is missing (fail loud)', () => { expect(() => renderTemplate({ bodyText: 'Hi {{firstName}}', variables: ['firstName'] }, {})).toThrow(MissingTemplateVariableError); }); it('renders only the parts that are present', () => { const out = renderTemplate({ bodyText: 'code {{code}}', variables: ['code'] }, { code: '123' }); expect(out.text).toBe('code 123'); expect(out.subject).toBeUndefined(); expect(out.html).toBeUndefined(); }); });