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 }}
{% if line2 %}{{ line2 }}
{% endif %}{{ town }}
{{ postcode }}' // Act const withoutLine2 = evaluate({ template, data: { line1: '10 Downing Street', line2: '', town: 'London', postcode: 'SW1A 2AA' }, }) const withLine2 = evaluate({ template, data: { line1: '221B Baker Street', line2: 'Marylebone', town: 'London', postcode: 'NW1 6XE' }, }) // Assert expect(withoutLine2).toBe('10 Downing Street
London
SW1A 2AA') expect(withLine2).toBe('221B Baker Street
Marylebone
London
NW1 6XE') }) }) describe('assertTemplateIsAllowed()', () => { it.each([ ['{% import "macros.njk" as m %}', 'import'], ['{% from "macros.njk" import link %}', 'from'], ['{% include "partial.njk" %}', 'include'], ['{% extends "layout.njk" %}', 'extends'], ['{% macro link(href, text) %}{{ text }}{% endmacro %}', 'macro'], ])('should reject %s', (template, tag) => { // Arrange / Act / Assert expect(() => assertTemplateIsAllowed(template)).toThrow(new RegExp(`\\{% ${tag} %\\}`)) }) it('should reject whitespace-trimmed forbidden tags', () => { // Arrange / Act / Assert expect(() => assertTemplateIsAllowed('{%-include "x.njk" -%}')).toThrow(/include/) }) it('should allow templates that only use interpolation and filters', () => { // Arrange / Act / Assert expect(() => assertTemplateIsAllowed('

{{ 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/) }) }) })