import { describe, it, expect, vi } from 'vitest'; import { extractPlaceholders, substitutePlaceholders, hasUnresolvedPlaceholders, validateTemplateName, getAutoFilledPlaceholders, getPlaceholdersFromTemplate, isBuiltInPlaceholder, filterTemplates, exportTemplates, importTemplates, spliceTemplateAboveSignature, composeBodyHasUserContent, } from '../template-utils'; import type { EmailTemplate } from '../template-types'; function makeTemplate(overrides: Partial = {}): EmailTemplate { return { id: 'test-id', name: 'Test Template', subject: '', body: '', isHTML: false, category: '', isFavorite: false, createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } describe('extractPlaceholders', () => { it('extracts single placeholder', () => { expect(extractPlaceholders('Hello {{name}}')).toEqual(['name']); }); it('extracts multiple placeholders', () => { const result = extractPlaceholders('{{greeting}} {{name}}, welcome to {{company}}'); expect(result).toEqual(['greeting', 'name', 'company']); }); it('deduplicates repeated placeholders', () => { expect(extractPlaceholders('{{name}} and {{name}}')).toEqual(['name']); }); it('returns empty array for no placeholders', () => { expect(extractPlaceholders('No placeholders here')).toEqual([]); }); it('handles empty string', () => { expect(extractPlaceholders('')).toEqual([]); }); it('ignores malformed placeholders', () => { expect(extractPlaceholders('{{}} {name} {{ name }}')).toEqual([]); }); it('handles underscored names', () => { expect(extractPlaceholders('{{first_name}} {{last_name}}')).toEqual(['first_name', 'last_name']); }); }); describe('substitutePlaceholders', () => { it('replaces a single placeholder', () => { expect(substitutePlaceholders('Hello {{name}}', { name: 'Alice' })).toBe('Hello Alice'); }); it('replaces multiple placeholders', () => { const result = substitutePlaceholders('{{greeting}} {{name}}', { greeting: 'Hi', name: 'Bob', }); expect(result).toBe('Hi Bob'); }); it('leaves unresolved placeholders', () => { expect(substitutePlaceholders('{{known}} {{unknown}}', { known: 'yes' })).toBe('yes {{unknown}}'); }); it('sanitizes XSS in values', () => { const result = substitutePlaceholders('{{name}}', { name: '' }); expect(result).not.toContain('' }], }); const body = importTemplates(json).templates[0].body; expect(body).toContain('Hi {{name}}'); expect(body).not.toContain('onerror'); expect(body).not.toContain(' { const result = importTemplates('not json'); expect(result.templates).toHaveLength(0); expect(result.errors).toContain('invalid_json'); }); it('returns error for wrong type', () => { const result = importTemplates(JSON.stringify({ type: 'other', version: 1, templates: [] })); expect(result.errors).toContain('invalid_type'); }); it('returns error for unsupported version', () => { const result = importTemplates(JSON.stringify({ type: 'webmail-templates', version: 99, templates: [] })); expect(result.errors).toContain('unsupported_version'); }); it('returns error for non-object input', () => { const result = importTemplates('"just a string"'); expect(result.errors).toContain('invalid_format'); }); it('skips entries without name', () => { const json = JSON.stringify({ type: 'webmail-templates', version: 1, templates: [{ subject: 'no name' }, { name: 'Valid' }], }); const result = importTemplates(json); expect(result.templates).toHaveLength(1); expect(result.templates[0].name).toBe('Valid'); expect(result.errors).toContain('missing_template_name'); }); it('sanitizes imported values against XSS', () => { const json = JSON.stringify({ type: 'webmail-templates', version: 1, templates: [{ name: '', subject: '' }], }); const result = importTemplates(json); expect(result.templates[0].name).not.toContain('onerror'); expect(result.templates[0].subject).not.toContain('