import {findTemplateVars, renderTemplate, TemplateError} from '../src'; describe('findTemplateVars', () => { test('extracts simple variables', () => { const result = findTemplateVars('Hello {{ name }}!'); expect(result).toEqual({ name: '' }); }); test('extracts nested object paths', () => { const result = findTemplateVars('{{ user.name }} is {{ user.age }}'); expect(result).toEqual({ user: { name: '', age: '' } }); }); test('extracts variables from if statements', () => { const result = findTemplateVars('{{ ? active }}{{ message }}{{ /? }}'); expect(result).toEqual({ active: '', message: '' }); }); test('extracts variables from else-if conditions', () => { const result = findTemplateVars('{{ ? status == "paid" }}PAID{{ !? status == "pending" }}{{ value }}{{ /? }}'); expect(result).toEqual({ status: '', value: '' }); }); test('extracts array reference from loops', () => { const result = findTemplateVars('{{ * item in items }}{{ item }}{{ /* }}'); expect(result).toEqual({ items: '' }); }); test('excludes loop element variable', () => { const result = findTemplateVars('{{ * item in items }}{{ item.name }}{{ /* }}'); expect(result).toEqual({ items: '' }); expect(result).not.toHaveProperty('item'); }); test('excludes loop index variable', () => { const result = findTemplateVars('{{ * (item, i) in items }}{{ i }}:{{ item }}{{ /* }}'); expect(result).toEqual({ items: '' }); expect(result).not.toHaveProperty('item'); expect(result).not.toHaveProperty('i'); }); test('extracts external vars used inside loops', () => { const result = findTemplateVars('{{ * item in items }}{{ item }}-{{ prefix }}{{ /* }}'); expect(result).toEqual({ items: '', prefix: '' }); }); test('handles nested loops', () => { const result = findTemplateVars('{{ * row in rows }}{{ * col in row.cols }}{{ col }}{{ /* }}{{ /* }}'); expect(result).toEqual({ rows: '' }); expect(result).not.toHaveProperty('row'); expect(result).not.toHaveProperty('col'); }); test('extracts from complex nested template', () => { const tpl = ` {{ ? items.length > 0 }} {{ * (item, i) in items }} {{ i }}. {{ item.name }}: {{ currency }}{{ item.price }} {{ /* }} Total: {{ total }} {{ !? }} {{ emptyMessage }} {{ /? }}`; const result = findTemplateVars(tpl); expect(result).toEqual({ items: '', currency: '', total: '', emptyMessage: '' }); expect(result).not.toHaveProperty('item'); expect(result).not.toHaveProperty('i'); }); test('ignores template control syntax', () => { const result = findTemplateVars('{{ < header.html }}{{ > layout.html:content }}content{{ /> }}'); expect(result).toEqual({}); }); test('handles multiple variables in expressions', () => { const result = findTemplateVars('{{ firstName + " " + lastName }}'); expect(result).toEqual({ firstName: '', lastName: '' }); }); }); describe('renderTemplate', () => { test('basic variable interpolation', async () => { const result = await renderTemplate('Hello {{ name }}!', { name: 'World' }); expect(result).toBe('Hello World!'); }); test('nested object access', async () => { const result = await renderTemplate('{{ user.name }} is {{ user.age }}', { user: { name: 'Alice', age: 25 } }); expect(result).toBe('Alice is 25'); }); test('method calls', async () => { const result = await renderTemplate('{{ price.toFixed(2) }}', { price: 9.5 }); expect(result).toBe('9.50'); }); test('if statement true', async () => { const result = await renderTemplate('{{ ? active }}YES{{ /? }}', { active: true }); expect(result).toBe('YES'); }); test('if statement false', async () => { const result = await renderTemplate('{{ ? active }}YES{{ /? }}', { active: false }); expect(result).toBe(''); }); test('if-else', async () => { const result = await renderTemplate('{{ ? active }}YES{{ !? }}NO{{ /? }}', { active: false }); expect(result).toBe('NO'); }); test('if-elseif-else', async () => { const tpl = '{{ ? status == "paid" }}PAID{{ !? status == "pending" }}PENDING{{ !? }}OTHER{{ /? }}'; expect(await renderTemplate(tpl, { status: 'paid' })).toBe('PAID'); expect(await renderTemplate(tpl, { status: 'pending' })).toBe('PENDING'); expect(await renderTemplate(tpl, { status: 'failed' })).toBe('OTHER'); }); test('nested if statements', async () => { const tpl = '{{ ? a }}A{{ ? b }}B{{ /? }}{{ /? }}'; expect(await renderTemplate(tpl, { a: true, b: true })).toBe('AB'); expect(await renderTemplate(tpl, { a: true, b: false })).toBe('A'); expect(await renderTemplate(tpl, { a: false, b: true })).toBe(''); }); test('for loop', async () => { const tpl = '{{ * item in items }}{{ item }}{{ /* }}'; const result = await renderTemplate(tpl, { items: ['a', 'b', 'c'] }); expect(result).toBe('a\nb\nc'); }); test('for loop with index', async () => { const tpl = '{{ * (item, i) in items }}{{ i }}:{{ item }}{{ /* }}'; const result = await renderTemplate(tpl, { items: ['a', 'b'] }); expect(result).toBe('0:a\n1:b'); }); test('for loop with objects', async () => { const tpl = '{{ * user in users }}{{ user.name }}{{ /* }}'; const result = await renderTemplate(tpl, { users: [{ name: 'Alice' }, { name: 'Bob' }] }); expect(result).toBe('Alice\nBob'); }); test('for loop error on non-array', async () => { await expect( renderTemplate('{{ * x in notArray }}{{ x }}{{ /* }}', { notArray: 'string' }) ).rejects.toThrow(TemplateError); }); test('import template', async () => { const fetch = async (file: string) => { if (file === 'header.html') return 'HEADER: {{ title }}'; throw new Error('Not found'); }; const result = await renderTemplate('{{ < header.html }}', { title: 'Test' }, fetch); expect(result).toBe('HEADER: Test'); }); test('import template error', async () => { await expect( renderTemplate('{{ < missing.html }}', {}) ).rejects.toThrow(TemplateError); }); test('extend template', async () => { const fetch = async (file: string) => { if (file === 'layout.html') return '
{{ content }}
'; throw new Error('Not found'); }; const result = await renderTemplate('{{ > layout.html:content }}Hello{{ /> }}', {}, fetch); expect(result).toBe('
Hello
'); }); test('date object', async () => { const result = await renderTemplate('{{ date.year }}', {}); expect(result).toBe(new Date().getFullYear().toString()); }); test('evaluation error', async () => { await expect( renderTemplate('{{ undefined.property }}', {}) ).rejects.toThrow(TemplateError); }); test('complex nested example', async () => { const tpl = ` {{ ? items.length > 0 }} {{ * (item, i) in items }} {{ i + 1 }}. {{ item.name }}: \${{ item.price.toFixed(2) }} {{ /* }} Total: \${{ total.toFixed(2) }} {{ !? }} No items {{ /? }}`; const result = await renderTemplate(tpl, { items: [ { name: 'A', price: 10 }, { name: 'B', price: 20 } ], total: 30 }); expect(result.trim()).toContain('1. A: $10.00'); expect(result.trim()).toContain('2. B: $20.00'); expect(result.trim()).toContain('Total: $30.00'); }); });