import { assertTemplateIsAllowed, NunjucksGenerators, nunjucksFunctions } from './nunjucksGenerators' describe('nunjucksGenerators', () => { const registry = nunjucksFunctions.build() describe('String evaluator', () => { const evaluate = registry.String.evaluate it('should render a plain template', () => { // Arrange / Act const result = evaluate({ template: '
Hello world
' }) // Assert expect(result).toBe('Hello world
') }) it('should interpolate values from data', () => { // Arrange / Act const result = evaluate({ template: 'Hello {{ name }}
', data: { name: 'Ada' } }) // Assert expect(result).toBe('Hello Ada
') }) it('should autoescape HTML in values', () => { // Arrange / Act const result = evaluate({ template: '{{ message }}
', data: { message: '' }, }) // Assert expect(result).toBe('<script>alert("xss")</script>
') }) it('should allow raw HTML via the safe filter', () => { // Arrange / Act const result = evaluate({ template: '{{ trusted | safe }}
', data: { trusted: 'bold' }, }) // Assert expect(result).toBe('bold
') }) it('should handle if blocks', () => { // Arrange / Act const shown = evaluate({ template: '{% if flag %}yes{% endif %}', data: { flag: true } }) const hidden = evaluate({ template: '{% if flag %}yes{% endif %}', data: { flag: false } }) // Assert expect(shown).toBe('yes') expect(hidden).toBe('') }) it('should handle for loops', () => { // Arrange / Act const result = evaluate({ template: '{% for x in items %}{{ x }},{% endfor %}', data: { items: ['a', 'b', 'c'] }, }) // Assert expect(result).toBe('a,b,c,') }) it('should default to empty data when omitted', () => { // Arrange / Act const result = evaluate({ template: '{{ missing }}' }) // Assert expect(result).toBe('') }) it('should treat undefined keys as falsy without throwing', () => { // Arrange / Act const result = evaluate({ template: '{% if x %}shown{% else %}hidden{% endif %}', data: {} }) // Assert expect(result).toBe('hidden') }) it('should return the same result when rendering a cached template with new data', () => { // Arrange: the same template string is cached after first render. const template = '{{ value }}
' // Act: render the cached template twice with different data. const first = evaluate({ template, data: { value: 'one' } }) const second = evaluate({ template, data: { value: 'two' } }) // Assert: each render uses fresh data even though the compiled template is reused. expect(first).toBe('one
') expect(second).toBe('two
') }) it('should render the address-composition template', () => { // Arrange const template = '{{ line1 }}{{ name | upper }}
')).not.toThrow() }) it('should allow {% if %}, {% for %}, and {% set %}', () => { // Arrange const template = ` {% set full = first + ' ' + last %} {% if full %}{{ full }}
{% endif %} {% for tag in tags %}{{ tag }}{% endfor %} ` // Act / Assert expect(() => assertTemplateIsAllowed(template)).not.toThrow() }) it('should not false-positive on identifiers that start with a forbidden name', () => { // Arrange / Act / Assert expect(() => assertTemplateIsAllowed('{% if importantThing %}yes{% endif %}')).not.toThrow() expect(() => assertTemplateIsAllowed('{% set includes = [] %}')).not.toThrow() }) it('should reject at render time when a forbidden tag slips past the prepare hook', () => { // Arrange const evaluate = registry.String.evaluate // Act / Assert: calling the generator evaluator directly still catches it on cache miss. expect(() => evaluate({ template: '{% include "x.njk" %}' })).toThrow(/include/) }) }) describe('NunjucksGenerators.String()', () => { it('should return a generator builder for allowed templates', () => { // Arrange / Act const expr = NunjucksGenerators.String({ template: 'Hello {{ name }}
', data: { name: 'Ada' }, }) // Assert: returns a chainable generator builder, not a string. expect(expr).toBeDefined() expect(typeof expr).toBe('object') }) it('should reject forbidden tags at author-call time via the prepare hook', () => { // Arrange / Act / Assert expect(() => NunjucksGenerators.String({ template: '{% include "external.njk" %}', }), ).toThrow(/include/) }) }) })