# TOC - [Twig.js Blocks ->](#twigjs-blocks--) - [block function ->](#twigjs-blocks---block-function--) - [block shorthand ->](#twigjs-blocks---block-shorthand--) - [Twig.js Control Structures ->](#twigjs-control-structures--) - [if tag ->](#twigjs-control-structures---if-tag--) - [for tag ->](#twigjs-control-structures---for-tag--) - [set tag ->](#twigjs-control-structures---set-tag--) - [Twig.js Core ->](#twigjs-core--) - [Key Notation ->](#twigjs-core---key-notation--) - [Context ->](#twigjs-core---context--) - [Twig.js Embed ->](#twigjs-embed--) - [Twig.js Expressions ->](#twigjs-expressions--) - [Basic Operators ->](#twigjs-expressions---basic-operators--) - [Comparison Operators ->](#twigjs-expressions---comparison-operators--) - [Other Operators ->](#twigjs-expressions---other-operators--) - [Twig.js Extensions ->](#twigjs-extensions--) - [Twig.js Filters ->](#twigjs-filters--) - [url_encode ->](#twigjs-filters---url_encode--) - [json_encode ->](#twigjs-filters---json_encode--) - [upper ->](#twigjs-filters---upper--) - [lower ->](#twigjs-filters---lower--) - [capitalize ->](#twigjs-filters---capitalize--) - [title ->](#twigjs-filters---title--) - [length ->](#twigjs-filters---length--) - [sort ->](#twigjs-filters---sort--) - [reverse ->](#twigjs-filters---reverse--) - [keys ->](#twigjs-filters---keys--) - [merge ->](#twigjs-filters---merge--) - [join ->](#twigjs-filters---join--) - [default ->](#twigjs-filters---default--) - [date ->](#twigjs-filters---date--) - [replace ->](#twigjs-filters---replace--) - [format ->](#twigjs-filters---format--) - [striptags ->](#twigjs-filters---striptags--) - [escape ->](#twigjs-filters---escape--) - [e ->](#twigjs-filters---e--) - [nl2br ->](#twigjs-filters---nl2br--) - [truncate ->](#twigjs-filters---truncate--) - [trim ->](#twigjs-filters---trim--) - [number_format ->](#twigjs-filters---number_format--) - [slice ->](#twigjs-filters---slice--) - [abs ->](#twigjs-filters---abs--) - [first ->](#twigjs-filters---first--) - [split ->](#twigjs-filters---split--) - [batch ->](#twigjs-filters---batch--) - [last ->](#twigjs-filters---last--) - [raw ->](#twigjs-filters---raw--) - [round ->](#twigjs-filters---round--) - [Twig.js Loader ->](#twigjs-loader--) - [source ->](#twigjs-loader---source--) - [Twig.js Include ->](#twigjs-include--) - [Twig.js Functions ->](#twigjs-functions--) - [Built-in Functions ->](#twigjs-functions---built-in-functions--) - [range ->](#twigjs-functions---built-in-functions---range--) - [cycle ->](#twigjs-functions---built-in-functions---cycle--) - [date ->](#twigjs-functions---built-in-functions---date--) - [dump ->](#twigjs-functions---built-in-functions---dump--) - [block ->](#twigjs-functions---built-in-functions---block--) - [attribute ->](#twigjs-functions---built-in-functions---attribute--) - [template_from_string ->](#twigjs-functions---built-in-functions---template_from_string--) - [random ->](#twigjs-functions---built-in-functions---random--) - [min, max ->](#twigjs-functions---built-in-functions---min-max--) - [Twig.js Loaders ->](#twigjs-loaders--) - [custom loader ->](#twigjs-loaders---custom-loader--) - [Twig.js Macro ->](#twigjs-macro--) - [Twig.js Namespaces ->](#twigjs-namespaces--) - [Twig.js Optional Functionality ->](#twigjs-optional-functionality--) - [Twig.js Parsers ->](#twigjs-parsers--) - [custom parser ->](#twigjs-parsers---custom-parser--) - [Twig.js Path ->](#twigjs-path--) - [relativePath ->](#twigjs-path---relativepath--) - [url ->](#twigjs-path---relativepath---url--) - [path ->](#twigjs-path---relativepath---path--) - [parsePath ->](#twigjs-path---parsepath--) - [Twig.js Regression Tests ->](#twigjs-regression-tests--) - [Twig.js Tags ->](#twigjs-tags--) - [Twig.js Tests ->](#twigjs-tests--) - [empty test ->](#twigjs-tests---empty-test--) - [odd test ->](#twigjs-tests---odd-test--) - [even test ->](#twigjs-tests---even-test--) - [divisibleby test ->](#twigjs-tests---divisibleby-test--) - [defined test ->](#twigjs-tests---defined-test--) - [none test ->](#twigjs-tests---none-test--) - [sameas test ->](#twigjs-tests---sameas-test--) - [iterable test ->](#twigjs-tests---iterable-test--) # Twig.js Blocks -> should load a parent template and render the default values. ```js twig({ id: 'remote-no-extends', path: 'test/templates/template.twig', async: false }); // Load the template twig({ref: 'remote-no-extends'}).render({ }).should.equal( "Default Title - body" ); ``` should understand {% endblock title %} syntax. ```js twig({ id: 'endblock-extended-syntax', path: 'test/templates/blocks-extended-syntax.twig', async: false }); // Load the template twig({ref: 'endblock-extended-syntax'}).render({ }).should.equal( "This is the only thing." ); ``` should load a child template and replace the parent block's content. ```js // Test loading a template from a remote endpoint twig({ id: 'child-extends', path: 'test/templates/child.twig', load: function(template) { template.render({ base: "template.twig" }).should.equal( "Other Title - child" ); done(); } }); ``` should have access to a parent block content. ```js // Test loading a template from a remote endpoint twig({ id: 'child-parent', path: 'test/templates/child-parent.twig', load: function(template) { template.render({ base: "template.twig", inner: ':value' }).should.equal( "Other Title - body:value:child" ); done(); } }); ``` should include blocks from another template for horizontal reuse. ```js // Test horizontal reuse twig({ id: 'use', path: 'test/templates/use.twig', load: function(template) { // Load the template template.render({ place: "diner" }).should.equal("Coming soon to a diner near you!" ); done(); } }); ``` should allow overriding of included blocks. ```js // Test overriding of included blocks twig({ id: 'use-override-block', path: 'test/templates/use-override-block.twig', load: function(template) { // Load the template template.render({ place: "diner" }).should.equal("Sorry, can't come to a diner today." ); done(); } }); ``` should allow overriding of included nested blocks. ```js // Test overriding of included blocks twig({ id: 'use-override-nested-block', path: 'test/templates/use-override-nested-block.twig', load: function(template) { // Load the template template.render().should.equal("parent:new-child1:new-child2"); done(); } }); ``` should make the contents of blocks available after they're rendered. ```js // Test rendering and loading one block twig({ id: 'blocks', path: 'test/templates/blocks.twig', load: function(template) { // Render the template with the blocks parameter template.render({ place: "block" }, {output: 'blocks'}).msg.should.equal("Coming soon to a block near you!" ); done(); } }); ``` should render nested blocks. ```js // Test rendering of blocks within blocks twig({ id: 'blocks-nested', path: 'test/templates/blocks-nested.twig', load: function(template) { template.render({ }).should.equal( "parent:child" ) done(); } }) ``` should render extended nested blocks. ```js // Test rendering of blocks within blocks twig({ id: 'child-blocks-nested', path: 'test/templates/child-blocks-nested.twig', load: function(template) { template.render({ base: "template.twig" }).should.equal( "Default Title - parent:child" ); done(); } }) ``` should be able to extend to a absolute template path. ```js // Test loading a template from a remote endpoint twig({ base: 'test/templates', path: 'test/templates/a/child.twig', load: function(template) { template.render({ base: "b/template.twig" }).should.equal( "Other Title - child" ); done(); } }); ``` should extends blocks inline. ```js twig({ id: 'inline-parent-template', data: 'Title: {% block title %}parent{% endblock %}' }); twig({ allowInlineIncludes: true, data: '{% extends "inline-parent-template" %}{% block title %}child{% endblock %}' }).render().should.equal("Title: child"); ``` ## block function -> should render block content from an included block. ```js twig({ path: 'test/templates/block-function.twig', load: function(template) { template.render({ base: "block-function-parent.twig", val: "abcd" }) .should.equal( "Child content = abcd / Result: Child content = abcd" ); done(); } }) ``` should render block content from a parent block. ```js twig({ path: 'test/templates/block-parent.twig', load: function(template) { template.render({ base: "block-function-parent.twig" }) .should.equal( "parent block / Result: parent block" ); done(); } }) ``` should render block content with outer context. ```js twig({ path: 'test/templates/block-outer-context.twig', load: function(template) { template.render({ base: "block-outer-context.twig", items: ["twig", "js", "rocks"] }) .should.equal( "Hello twig!Hello js!Hello rocks!twigjsrocks" ); done(); } }) ``` should respect changes of the context made before calling the function. ```js twig({ data: '{% set foo = "original" %}{% block test %}{{ foo }}{% endblock %} {% set foo = "changed" %}{{ block("test") }}' }).render() .should.equal("original changed"); ``` ## block shorthand -> should render block content using shorthand syntax. ```js twig({ data: '{% set prefix = "shorthand" %}{% block title (prefix ~ " - " ~ block_value)|title %}' }) .render({block_value: 'test succeeded'}) .should.equal('Shorthand - Test Succeeded'); ``` should overload blocks from an extended template using shorthand syntax. ```js twig({ allowInlineIncludes: true, data: '{% extends "child-extends" %}{% block title "New Title" %}{% block body "new body uses the " ~ base ~ " template" %}' }) .render({ base: "template.twig" }) .should.equal( "New Title - new body uses the template.twig template" ); ``` # Twig.js Control Structures -> ## if tag -> should parse the contents of the if block if the expression is true. ```js var test_template = twig({data: '{% if test %}true{% endif%}'}); test_template.render({test: true}).should.equal("true" ); test_template.render({test: false}).should.equal("" ); ``` should call the if or else blocks based on the expression result. ```js var test_template = twig({data: '{% if test %}true{% endif%}'}); test_template.render({test: true}).should.equal("true" ); test_template.render({test: false}).should.equal("" ); ``` should support elseif. ```js var test_template = twig({data: '{% if test %}1{% elseif other %}2{%else%}3{% endif%}'}); test_template.render({test: true, other:false}).should.equal("1" ); test_template.render({test: true, other:true}).should.equal("1" ); test_template.render({test: false, other:true}).should.equal("2" ); test_template.render({test: false, other:false}).should.equal("3" ); ``` should be able to nest. ```js var test_template = twig({data: '{% if test %}{% if test2 %}true{% else %}false{% endif%}{% else %}not{% endif%}'}); test_template.render({test: true, test2: true}).should.equal("true" ); test_template.render({test: true, test2: false}).should.equal("false" ); test_template.render({test: false, test2: true}).should.equal("not" ); test_template.render({test: false, test2: false}).should.equal("not" ); ``` should support newlines in if statement. ```js var test_template = twig({data: '{% if test or\r\nother %}true{% endif%}'}); test_template.render({test: true, other: false}).should.equal("true" ); test_template.render({test: false, other: false}).should.equal("" ); ``` ## for tag -> should provide value only for array input. ```js var test_template = twig({data: '{% for value in test %}{{ value }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("1234" ); test_template.render({test: []}).should.equal("" ); ``` should provide both key and value for array input. ```js var test_template = twig({data: '{% for key,value in test %}{{key}}:{{ value }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("0:11:22:33:4" ); test_template.render({test: []}).should.equal("" ); ``` should provide value only for object input. ```js var test_template = twig({data: '{% for value in test %}{{ value }}{% endfor %}'}); test_template.render({test: {one: 1, two: 2, three: 3}}).should.equal("123" ); test_template.render({test: {}}).should.equal("" ); ``` should provide both key and value for object input. ```js var test_template = twig({data: '{% for key, value in test %}{{key}}:{{ value }}{% endfor %}'}); test_template.render({test: {one: 1, two: 2, three: 3}}).should.equal("one:1two:2three:3" ); test_template.render({test: {}}).should.equal("" ); ``` should support else if the input is empty. ```js var test_template = twig({data: '{% for key,value in test %}{{ value }}{% else %}else{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("1234" ); test_template.render({test: []}).should.equal("else" ); ``` should be able to nest. ```js var test_template = twig({data: '{% for key,list in test %}{% for val in list %}{{ val }}{%endfor %}.{% else %}else{% endfor %}'}); test_template.render({test: [[1,2],[3,4],[5,6]]}).should.equal("12.34.56." ); test_template.render({test: []}).should.equal("else" ); ``` should have a loop context item available for arrays. ```js var test_template = twig({data: '{% for key,value in test %}{{ loop.index }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("1234" ); test_template = twig({data: '{% for key,value in test %}{{ loop.index0 }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("0123" ); test_template = twig({data: '{% for key,value in test %}{{ loop.revindex }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("4321" ); test_template = twig({data: '{% for key,value in test %}{{ loop.revindex0 }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("3210" ); test_template = twig({data: '{% for key,value in test %}{{ loop.length }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("4444" ); test_template = twig({data: '{% for key,value in test %}{{ loop.first }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("truefalsefalsefalse" ); test_template = twig({data: '{% for key,value in test %}{{ loop.last }}{% endfor %}'}); test_template.render({test: [1,2,3,4]}).should.equal("falsefalsefalsetrue" ); ``` should have a loop context item available for objects. ```js var test_template = twig({data: '{% for key,value in test %}{{ loop.index }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("1234" ); test_template = twig({data: '{% for key,value in test %}{{ loop.index0 }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("0123" ); test_template = twig({data: '{% for key,value in test %}{{ loop.revindex }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("4321" ); test_template = twig({data: '{% for key,value in test %}{{ loop.revindex0 }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("3210" ); test_template = twig({data: '{% for key,value in test %}{{ loop.length }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("4444" ); test_template = twig({data: '{% for key,value in test %}{{ loop.first }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("truefalsefalsefalse" ); test_template = twig({data: '{% for key,value in test %}{{ loop.last }}{% endfor %}'}); test_template.render({test: {a:1,b:2,c:3,d:4}}).should.equal("falsefalsefalsetrue" ); ``` should have a loop context item available in child loops objects. ```js var test_template = twig({data: '{% for value in test %}{% for value in inner %}({{ loop.parent.loop.index }},{{ loop.index }}){% endfor %}{% endfor %}'}); test_template.render({test: {a:1,b:2}, inner:[1,2,3]}).should.equal("(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)"); ``` should support conditionals on for loops. ```js var test_template = twig({data: '{% for value in test if false %}{{ value }},{% endfor %}'}); test_template.render({test: ["one", "two", "a", "b", "other"]}).should.equal(""); test_template = twig({data: '{% for value in test if true %}{{ value }}{% endfor %}'}); test_template.render({test: ["a", "s", "d", "f"]}).should.equal("asdf"); test_template = twig({data: '{% for value in test if value|length > 2 %}{{ value }},{% endfor %}'}); test_template.render({test: ["one", "two", "a", "b", "other"]}).should.equal("one,two,other,"); test_template = twig({data: '{% for key,item in test if item.show %}{{key}}:{{ item.value }},{% endfor %}'}); test_template.render({test: { a: {show:true, value: "one"}, b: {show:false, value: "two"}, c: {show:true, value: "three"}}}).should.equal("a:one,c:three,"); ``` ## set tag -> should not set the global context from within a for loop. ```js var test_template = twig({data: '{% for value in [1] %}{% set foo="right" %}{% endfor %}{{ foo }}'}); test_template.render().should.equal(""); ``` should set the global context from within a for loop when the variable is initialized outside of the loop. ```js var test_template = twig({data: '{% set foo="wrong" %}{% for value in [1] %}{% set foo="right" %}{% endfor %}{{ foo }}'}); test_template.render().should.equal("right"); ``` should set the global context from within a nested for loop when the variable is initialized outside of the loop. ```js var test_template = twig({data: '{% set k = 0 %}{% for i in 0..2 %}{% for j in 0..2 %}{{ k }}{% set k = k + 1 %}{% endfor %}{% endfor %}'}); test_template.render().should.equal("012345678"); ``` # Twig.js Core -> should save and load a template by reference. ```js // Define and save a template twig({ id: 'test', data: '{{ "test" }}' }); // Load and render the template twig({ref: 'test'}).render() .should.equal("test"); ``` should ignore comments. ```js twig({data: 'good {# comment #}morning'}).render().should.equal("good morning"); twig({data: 'good{#comment#}morning'}).render().should.equal("goodmorning"); ``` should ignore output tags within comments. ```js twig({data: 'good {# {{ "Hello" }} #}morning'}).render().should.equal("good morning"); twig({data: 'good{#c}}om{{m{{ent#}morning'}).render().should.equal("goodmorning"); ``` should ignore logic tags within comments. ```js twig({data: 'test {# {% bad syntax if not in comment %} #}test'}).render().should.equal("test test"); twig({data: '{##}{##}test{# %}}}%}%{%{{% #}pass'}).render().should.equal("testpass"); ``` should ignore quotation marks within comments. ```js twig({data: "good {# don't stop #}morning"}).render().should.equal("good morning"); twig({data: 'good{#"dont stop"#}morning'}).render().should.equal("goodmorning"); twig({data: 'good {# "don\'t stop" #}morning'}).render().should.equal("good morning"); twig({data: 'good{#"\'#}morning'}).render().should.equal("goodmorning"); twig({data: 'good {#"\'"\'"\'#} day'}).render().should.equal("good day"); twig({data: "a {# ' #}b{# ' #} c"}).render().should.equal("a b c"); ``` should be able to parse output tags with tag ends in strings. ```js // Really all we care about here is not throwing exceptions. twig({data: '{{ "test" }}'}).render().should.equal("test"); twig({data: '{{ " }} " }}'}).render().should.equal(" }} "); twig({data: '{{ " \\"}} " }}'}).render().should.equal(' "}} '); twig({data: "{{ ' }} ' }}"}).render().should.equal(" }} "); twig({data: "{{ ' \\'}} ' }}"}).render().should.equal(" '}} "); twig({data: '{{ " \'}} " }}'}).render().should.equal(" '}} "); twig({data: "{{ ' \"}} ' }}"}).render().should.equal(' "}} '); ``` should be able to parse whitespace control output tags. ```js twig({data: ' {{- "test" -}}'}).render().should.equal("test"); twig({data: ' {{- "test" -}} '}).render().should.equal("test"); twig({data: '\n{{- "test" -}}'}).render().should.equal("test"); twig({data: '{{- "test" -}}\n'}).render().should.equal("test"); twig({data: '\n{{- "test" -}}\n'}).render().should.equal("test"); twig({data: '\t{{- "test" -}}\t'}).render().should.equal("test"); twig({data: '\n\t{{- "test" -}}\n\t'}).render().should.equal("test"); twig({data: '123\n\t{{- "test" -}}\n\t456'}).render().should.equal("123test456"); twig({data: '\n{{- orp -}}\n'}).render({ orp: "test"}).should.equal("test"); twig({data: '\n{{- [1,2 ,1+2 ] -}}\n'}).render().should.equal("1,2,3"); twig({data: ' {{- "test" -}} {{- "test" -}}'}).render().should.equal("testtest"); twig({data: '{{ "test" }} {{- "test" -}}'}).render().should.equal("testtest"); twig({data: '{{- "test" -}} {{ "test" }}'}).render().should.equal("testtest"); twig({data: '<>{{- "test" -}}<>'}).render().should.equal("<>test<>"); ``` should be able to parse mismatched opening whitespace control output tags. ```js twig({data: ' {{- "test" }}'}).render().should.equal("test"); twig({data: '{{- "test" }}\n'}).render().should.equal("test\n"); twig({data: '\t{{- "test" }}\t'}).render().should.equal("test\t"); twig({data: '123\n\t{{- "test" }}\n\t456'}).render().should.equal("123test\n\t456"); twig({data: '\n{{- [1,2 ,1+2 ] }}\n'}).render().should.equal("1,2,3\n"); twig({data: ' {{- "test" }} {{- "test" }}'}).render().should.equal("testtest"); twig({data: '{{ "test" }} {{- "test" }}'}).render().should.equal("testtest"); twig({data: ' {{- "test" }} {{ "test" }}'}).render().should.equal("test test"); twig({data: ' {{- "test" }} {{- "test" -}}'}).render().should.equal("testtest"); twig({data: '<>{{- "test" }}'}).render().should.equal("<>test"); ``` should be able to parse mismatched closing whitespace control output tags. ```js twig({data: ' {{ "test" -}}'}).render().should.equal(" test"); twig({data: '\n{{ "test" -}}\n'}).render().should.equal("\ntest"); twig({data: '\t{{ "test" -}}\t'}).render().should.equal("\ttest"); twig({data: '123\n\t{{ "test" -}}\n\t456'}).render().should.equal("123\n\ttest456"); twig({data: '\n{{ [1,2 ,1+2 ] -}}\n'}).render().should.equal("\n1,2,3"); twig({data: ' {{ "test" -}} {{ "test" -}}'}).render().should.equal(" testtest"); twig({data: '{{ "test" }} {{ "test" -}} '}).render().should.equal("test test"); twig({data: ' {{ "test" -}} {{ "test" }} '}).render().should.equal(" testtest "); twig({data: ' {{ "test" -}} {{- "test" -}}'}).render().should.equal(" testtest"); twig({data: '{{ "test" -}}<>'}).render().should.equal("test<>"); ``` should be able to parse whitespace control logic tags. ```js // Newlines directly after logic tokens are ignored // So use double newlines twig({data: '{%- if true -%}{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '{%- if true -%}{{ "test" }}{%- endif -%}'}).render().should.equal("test"); twig({data: ' {%- if true -%} {{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '\n{%- if true -%}\n\n{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '\n\t{%- if true -%}\n\n\t{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '123\n\t{%- if true -%}\n\n\t{{ "test" }}{% endif %}456'}).render().should.equal("123test456"); twig({data: '\n\t{%- if true -%}\n\n\t{{ [1,2 ,1+2 ] }}{% endif %}'}).render().should.equal("1,2,3"); twig({data: '<>{%- if true -%}test{% endif %}<>'}).render().should.equal("<>test<>"); ``` should be able to parse mismatched opening whitespace control logic tags. ```js twig({data: '{%- if true %}{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '{%- if true %}{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: ' {% if true %} {{ "test" }}{% endif %}'}).render().should.equal(" test"); twig({data: ' {%- if true %} {{ "test" }}{% endif %}'}).render().should.equal(" test"); twig({data: '\n{% if true %}\n\n{{ "test" }}{% endif %}'}).render().should.equal("\n\ntest"); twig({data: '\n{%- if true %}\n\n{{ "test" }}{% endif %}'}).render().should.equal("\ntest"); twig({data: '\n\t{%- if true %}\n\n\t{{ "test" }}{% endif %}'}).render().should.equal("\n\ttest"); twig({data: '123\n\t{%- if true %}\n\n\t{{ "test" }}{% endif %}456'}).render().should.equal("123\n\ttest456"); twig({data: '\n\t{%- if true %}\n\n\t{{ [1,2 ,1+2 ] }}{% endif %}'}).render().should.equal("\n\t1,2,3"); twig({data: '<>{%- if true %}test{% endif %}'}).render().should.equal("<>test"); ``` should be able to parse mismatched closing whitespace control logic tags. ```js twig({data: '{% if true %}{{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: '{% if true -%} {{ "test" }}{% endif %}'}).render().should.equal("test"); twig({data: ' {% if true -%} {{ "test" }}{% endif %}'}).render().should.equal(" test"); twig({data: ' {% if true -%} {{ "test" }}{% endif %}'}).render().should.equal(" test"); twig({data: '\n{% if true %}\n\n{{ "test" }}{% endif %}'}).render().should.equal("\n\ntest"); twig({data: '\n{% if true -%}\n\n{{ "test" }}{% endif %}'}).render().should.equal("\ntest"); twig({data: '\n\t{% if true -%}\n\n\t{{ "test" }}{% endif %}'}).render().should.equal("\n\ttest"); twig({data: '123\n\t{% if true -%}\n\n\t{{ "test" }}{% endif %}456'}).render().should.equal("123\n\ttest456"); twig({data: '\n\t{% if true -%}\n\n\t{{ [1,2 ,1+2 ] }}{% endif %}'}).render().should.equal("\n\t1,2,3"); twig({data: '{% if true -%}<>test{% endif %}'}).render().should.equal("<>test"); ``` should be able to output numbers. ```js twig({data: '{{ 12 }}'}).render().should.equal( "12" ); twig({data: '{{ 12.64 }}'}).render().should.equal( "12.64" ); twig({data: '{{ 0.64 }}'}).render().should.equal("0.64" ); ``` should be able to output booleans. ```js twig({data: '{{ true }}'}).render().should.equal( "true" ); twig({data: '{{ false }}'}).render().should.equal( "false" ); ``` should be able to output strings. ```js twig({data: '{{ "double" }}'}).render().should.equal("double"); twig({data: "{{ 'single' }}"}).render().should.equal('single'); twig({data: '{{ "dou\'ble" }}'}).render().should.equal("dou'ble"); twig({data: "{{ 'sin\"gle' }}"}).render().should.equal('sin"gle'); twig({data: '{{ "dou\\"ble" }}'}).render().should.equal("dou\"ble"); twig({data: "{{ 'sin\\'gle' }}"}).render().should.equal("sin'gle"); ``` should be able to output strings with newlines. ```js twig({data: "{{ 'a\nb\rc\r\nd' }}"}).render().should.equal("a\nb\rc\r\nd"); ``` should be able to output arrays. ```js twig({data: '{{ [1] }}'}).render().should.equal("1" ); twig({data: '{{ [1,2 ,3 ] }}'}).render().should.equal("1,2,3" ); twig({data: '{{ [1,2 ,3 , val ] }}'}).render({val: 4}).should.equal("1,2,3,4" ); twig({data: '{{ ["[to",\'the\' ,"string]" ] }}'}).render().should.equal('[to,the,string]' ); twig({data: '{{ ["[to",\'the\' ,"str\\"ing]" ] }}'}).render().should.equal('[to,the,str"ing]' ); ``` should be able to output parse expressions in an array. ```js twig({data: '{{ [1,2 ,1+2 ] }}'}).render().should.equal("1,2,3" ); twig({data: '{{ [1,2 ,3 , "-", [4,5, 6] ] }}'}).render({val: 4}).should.equal("1,2,3,-,4,5,6" ); twig({data: '{{ [a,b ,(1+2) * a ] }}'}).render({a:1,b:2}).should.equal("1,2,3" ); ``` should be able to output variables. ```js twig({data: '{{ orp }}'}).render({ orp: "test"}).should.equal("test"); twig({data: '{{ val }}'}).render({ val: function() { return "test" }}).should.equal("test"); ``` should recognize null. ```js twig({data: '{{ null == val }}'}).render({val: null}).should.equal( "true" ); twig({data: '{{ null == val }}'}).render({val: undefined}).should.equal( "true" ); twig({data: '{{ null == val }}'}).render({val: "test"}).should.equal( "false" ); twig({data: '{{ null == val }}'}).render({val: 0}).should.equal( "false" ); twig({data: '{{ null == val }}'}).render({val: false}).should.equal( "false" ); ``` should recognize object literals. ```js twig({data: '{% set at = {"foo": "test", bar: "other", 1:"zip"} %}{{ at.foo ~ at.bar ~ at.1 }}'}).render().should.equal( "testotherzip" ); ``` should allow newlines in object literals. ```js twig({data: '{% set at = {\n"foo": "test",\rbar: "other",\r\n1:"zip"\n} %}{{ at.foo ~ at.bar ~ at.1 }}'}).render().should.equal( "testotherzip" ); ``` should recognize null in an object. ```js twig({data: '{% set at = {"foo": null} %}{{ at.foo == val }}'}).render({val: null}).should.equal( "true" ); ``` should allow int 0 as a key in an object. ```js twig({data: '{% set at = {0: "value"} %}{{ at.0 }}'}).render().should.equal( "value" ); ``` should support set capture. ```js twig({data: '{% set foo %}bar{% endset %}{{foo}}'}).render().should.equal( "bar" ); ``` should support raw data. ```js twig({ data: "before {% raw %}{{ test }} {% test2 %} {{{% endraw %} after" }).render().should.equal( "before {{ test }} {% test2 %} {{ after" ); ``` should support raw data using 'verbatim' tag. ```js twig({ data: "before {% verbatim %}{{ test }} {% test2 %} {{{% endverbatim %} after" }).render().should.equal( "before {{ test }} {% test2 %} {{ after" ); ``` ## Key Notation -> should support dot key notation. ```js twig({data: '{{ key.value }} {{ key.sub.test }}'}).render({ key: { value: "test", sub: { test: "value" } } }).should.equal("test value"); ``` should support square bracket key notation. ```js twig({data: '{{ key["value"] }} {{ key[\'sub\']["test"] }}'}).render({ key: { value: "test", sub: { test: "value" } } }).should.equal("test value"); ``` should support mixed dot and bracket key notation. ```js twig({data: '{{ key["value"] }} {{ key.sub[key.value] }} {{ s.t["u"].v["w"] }}'}).render({ key: { value: "test", sub: { test: "value" } }, s: { t: { u: { v: { w: 'x' } } } } }).should.equal("test value x" ); ``` should support dot key notation after a function. ```js var test_template = twig({data: '{{ key.fn().value }}'}); var output = test_template.render({ key: { fn: function() { return { value: "test" } } } }); output.should.equal("test"); ``` should support bracket key notation after a function. ```js var test_template = twig({data: '{{ key.fn()["value"] }}'}); var output = test_template.render({ key: { fn: function() { return { value: "test 2" } } } }); output.should.equal("test 2"); ``` should check for getKey methods if a key doesn't exist.. ```js twig({data: '{{ obj.value }}'}).render({ obj: { getValue: function() { return "val"; }, isValue: function() { return "not val"; } } }).should.equal("val"); ``` should check for isKey methods if a key doesn't exist.. ```js twig({data: '{{ obj.value }}'}).render({ obj: { isValue: function() { return "val"; } } }).should.equal("val"); ``` should check for getKey methods on prototype objects.. ```js var object = { getValue: function() { return "val"; } }; function Subobj() {}; Subobj.prototype = object; var subobj = new Subobj(); twig({data: '{{ obj.value }}'}).render({ obj: subobj }).should.equal("val"); ``` should return null if a period key doesn't exist.. ```js twig({data: '{{ obj.value == null }}'}).render({ obj: {} }).should.equal("true"); ``` should return null if a bracket key doesn't exist.. ```js twig({data: '{{ obj["value"] == null }}'}).render({ obj: {} }).should.equal("true"); ``` ## Context -> should be supported. ```js twig({data: '{{ _context.value }}'}).render({ value: "test" }).should.equal("test"); ``` should be an object even if it's not passed. ```js twig({data: '{{ _context|json_encode }}'}).render().should.equal("{}"); ``` should support {% set %} tag. ```js twig({data: '{% set value = "test" %}{{ _context.value }}'}).render().should.equal("test"); ``` should work correctly with properties named dynamically. ```js twig({data: '{{ _context[key] }}'}).render({ key: "value", value: "test" }).should.equal("test"); ``` should not allow to override context using {% set %}. ```js twig({data: '{% set _context = "test" %}{{ _context|json_encode }}'}).render().should.equal('{"_context":"test"}'); twig({data: '{% set _context = "test" %}{{ _context._context }}'}).render().should.equal("test"); ``` should support autoescape option. ```js twig({ autoescape: true, data: '{{ value }}' }).render({ value: "&" }).should.equal('<test>&</test>'); ``` should support autoescape option with alternative strategy. ```js twig({ autoescape: 'js', data: '{{ value }}' }).render({ value: "&" }).should.equal('\\x3Ctest\\x3E\\x26\\x3C\\x2Ftest\\x3E'); ``` should autoescape parent() output correctly. ```js twig({id: 'parent1', data: '{% block body %}

{{ value }}

{% endblock body %}'}); twig({ allowInlineIncludes: true, autoescape: true, data: '{% extends "parent1" %}{% block body %}{{ parent() }}{% endblock %}' }).render({ value: "&" }).should.equal('

<test>&</test>

'); ``` should use a correct context in the extended template. ```js twig({id: 'parent', data: '{% block body %}{{ value }}{% endblock body %}'}); twig({ allowInlineIncludes: true, data: '{% extends "parent" %}{% set value = "test" %}{% block body %}{{ parent() }}{% endblock %}' }).render().should.equal("test"); ``` should use a correct context in the included template. ```js twig({id: 'included', data: '{{ value }}\n{% set value = "inc" %}{{ value }}\n'}); twig({ allowInlineIncludes: true, data: '{% set value = "test" %}{% for i in [0, 1] %}{% include "included" %}{% endfor %}{{ value }}' }).render().should.equal("test\ninc\ntest\ninc\ntest"); ``` # Twig.js Embed -> it should load embed and render. ```js twig({ id: 'embed', path: 'test/templates/embed-simple.twig', async: false }); // Load the template twig({ref: 'embed'}).render({ }).trim().should.equal( ['START', 'A', 'new header', 'base footer', 'B', '', 'A', 'base header', 'base footer', 'extended', 'B', '', 'A', 'base header', 'extended', 'base footer', 'extended', 'B', '', 'A', 'Super cool new header', 'Cool footer', 'B', 'END'].join('\n') ); ``` # Twig.js Expressions -> ## Basic Operators -> should parse parenthesis. ```js var test_template = twig({data: '{{ a - (b + c) }}'}), d = {a: 10, b: 4, c: 2}, output = test_template.render(d); output.should.equal( (d.a - (d.b + d.c)).toString() ); ``` should parse nested parenthesis. ```js var test_template = twig({data: '{{ a - ((b) + (1 + c)) }}'}), d = {a: 10, b: 4, c: 2}, output = test_template.render(d); output.should.equal( (d.a - (d.b + 1 + d.c)).toString() ); ``` should add numbers. ```js var test_template = twig({data: '{{ a + b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal( (pair.a + pair.b).toString() ); }); ``` should subtract numbers. ```js var test_template = twig({data: '{{ a - b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal( (pair.a - pair.b).toString() ); }); ``` should multiply numbers. ```js var test_template = twig({data: '{{ a * b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a * pair.b).toString() ); }); ``` should divide numbers. ```js var test_template = twig({data: '{{ a / b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a / pair.b).toString() ); }); ``` should divide numbers and return an int result. ```js var test_template = twig({data: '{{ a // b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); // Get expected truncated result var c = parseInt(pair.a/pair.b); output.should.equal(c.toString() ); }); ``` should raise numbers to a power. ```js var test_template = twig({data: '{{ a ** b }}'}); var pow_test_data = [ {a: 2, b:3, c: 8} , {a: 4, b:.5, c: 2} , {a: 5, b: 1, c: 5} ]; pow_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal(pair.c.toString() ); }); ``` should concatanate values. ```js twig({data: '{{ "test" ~ a }}'}).render({a:1234}).should.equal("test1234"); twig({data: '{{ a ~ "test" ~ a }}'}).render({a:1234}).should.equal("1234test1234"); twig({data: '{{ "this" ~ "test" }}'}).render({a:1234}).should.equal("thistest"); // Test numbers var test_template = twig({data: '{{ a ~ b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal(pair.a.toString() + pair.b.toString()); }); // Test strings test_template = twig({data: '{{ a ~ b }}'}); string_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal(pair.a.toString() + pair.b.toString()); }); ``` should concatenate null and undefined values and not throw an exception. ```js twig({data: '{{ a ~ b }}'}).render().should.equal(""); twig({data: '{{ a ~ b }}'}).render({ a: null, b: null }).should.equal(""); ``` should handle multiple chained operations. ```js var data = {a: 4.5, b: 10, c: 12, d: -0.25, e:0, f: 65, g: 21, h: -0.0002}; var test_template = twig({data: '{{a/b+c*d-e+f/g*h}}'}); var output = test_template.render(data); var expected = data.a / data.b + data.c * data.d - data.e + data.f / data.g * data.h; output.should.equal(expected.toString()); ``` should handle parenthesis in chained operations. ```js var data = {a: 4.5, b: 10, c: 12, d: -0.25, e:0, f: 65, g: 21, h: -0.0002}; var test_template = twig({data: '{{a /(b+c )*d-(e+f)/(g*h)}}'}); var output = test_template.render(data); var expected = data.a / (data.b + data.c) * data.d - (data.e + data.f) / (data.g * data.h); output.should.equal(expected.toString()); ``` ## Comparison Operators -> should support less then. ```js var test_template = twig({data: '{{ a < b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a < pair.b).toString() ); }); ``` should support less then or equal. ```js var test_template = twig({data: '{{ a <= b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a <= pair.b).toString() ); }); ``` should support greater then. ```js var test_template = twig({data: '{{ a > b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a > pair.b).toString() ); }); ``` should support greater then or equal. ```js var test_template = twig({data: '{{ a >= b }}'}); numeric_test_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a >= pair.b).toString() ); }); ``` should support equals. ```js var test_template = twig({data: '{{ a == b }}'}); boolean_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a == pair.b).toString() ); }); equality_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a == pair.b).toString() ); }); ``` should support not equals. ```js var test_template = twig({data: '{{ a != b }}'}); boolean_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a != pair.b).toString() ); }); equality_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a != pair.b).toString() ); }); ``` should support boolean or. ```js var test_template = twig({data: '{{ a or b }}'}); boolean_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a || pair.b).toString() ); }); ``` should support boolean and. ```js var test_template = twig({data: '{{ a and b }}'}); boolean_data.forEach(function(pair) { var output = test_template.render(pair); output.should.equal((pair.a && pair.b).toString() ); }); ``` should support boolean not. ```js var test_template = twig({data: '{{ not a }}'}); test_template.render({a:false}).should.equal(true.toString()); test_template.render({a:true}).should.equal(false.toString()); ``` ## Other Operators -> should support the ternary operator. ```js var test_template = twig({data: '{{ a ? b:c }}'}) , output_t = test_template.render({a: true, b: "one", c: "two"}) , output_f = test_template.render({a: false, b: "one", c: "two"}); output_t.should.equal( "one" ); output_f.should.equal( "two" ); ``` should support the ternary operator with objects in it. ```js var test_template2 = twig({data: '{{ (a ? {"a":e+f}:{"a":1}).a }}'}) , output2 = test_template2.render({a: true, b: false, e: 1, f: 2}); output2.should.equal( "3" ); ``` should support the ternary operator inside objects. ```js var test_template2 = twig({data: '{{ {"b" : a or b ? {"a":e+f}:{"a":1} }.b.a }}'}) , output2 = test_template2.render({a: false, b: false, e: 1, f: 2}); output2.should.equal( "1" ); ``` should support in/containment functionality for arrays. ```js var test_template = twig({data: '{{ "a" in ["a", "b", "c"] }}'}); test_template.render().should.equal(true.toString()); var test_template = twig({data: '{{ "d" in ["a", "b", "c"] }}'}); test_template.render().should.equal(false.toString()); ``` should support not in/containment functionality for arrays. ```js var test_template = twig({data: '{{ "a" not in ["a", "b", "c"] }}'}); test_template.render().should.equal(false.toString()); var test_template = twig({data: '{{ "d" not in ["a", "b", "c"] }}'}); test_template.render().should.equal(true.toString()); ``` should support in/containment functionality for strings. ```js var test_template = twig({data: '{{ "at" in "hat" }}'}); test_template.render().should.equal(true.toString()); var test_template = twig({data: '{{ "d" in "not" }}'}); test_template.render().should.equal(false.toString()); ``` should support not in/containment functionality for strings. ```js var test_template = twig({data: '{{ "at" not in "hat" }}'}); test_template.render().should.equal(false.toString()); var test_template = twig({data: '{{ "d" not in "not" }}'}); test_template.render().should.equal(true.toString()); ``` should support in/containment functionality for objects. ```js var test_template = twig({data: '{{ "value" in {"key" : "value", "2": "other"} }}'}); test_template.render().should.equal(true.toString()); var test_template = twig({data: '{{ "d" in {"key_a" : "no"} }}'}); test_template.render().should.equal(false.toString()); ``` should support not in/containment functionality for objects. ```js var test_template = twig({data: '{{ "value" not in {"key" : "value", "2": "other"} }}'}); test_template.render().should.equal(false.toString()); var test_template = twig({data: '{{ "d" not in {"key_a" : "no"} }}'}); test_template.render().should.equal(true.toString()); ``` should support undefined and null for the in operator. ```js var test_template = twig({data: '{{ 0 in undefined }} {{ 0 in null }}'}); test_template.render().should.equal(' '); ``` should support expressions as object keys. ```js var test_template; test_template = twig({data: '{% set a = {(foo): "value"} %}{{ a.bar }}'}); test_template.render({foo: 'bar'}).should.equal('value'); test_template = twig({data: '{{ {(foo): "value"}.bar }}'}); test_template.render({foo: 'bar'}).should.equal('value'); ``` # Twig.js Extensions -> should be able to extend a meta-type tag. ```js var flags = {}; Twig.extend(function(Twig) { Twig.exports.extendTag({ type: "flag", regex: /^flag\s+(.+)$/, next: [ ], open: true, compile: function (token) { var expression = token.match[1]; // Compile the expression. token.stack = Twig.expression.compile.apply(this, [{ type: Twig.expression.type.expression, value: expression }]).stack; delete token.match; return token; }, parse: function (token, context, chain) { var name = Twig.expression.parse.apply(this, [token.stack, context]), output = ''; flags[name] = true; return { chain: false, output: output }; } }); }); var template = twig({data:"{% flag 'enabled' %}"}).render(); flags.enabled.should.equal(true); ``` should be able to extend paired tags. ```js // demo data var App = { user: "john", users: { john: {level: "admin"}, tom: {level: "user"} } }; Twig.extend(function(Twig) { // example of extending a tag type that would // restrict content to the specified "level" Twig.exports.extendTag({ type: "auth", regex: /^auth\s+(.+)$/, next: ["endauth"], // match the type of the end tag open: true, compile: function (token) { var expression = token.match[1]; // turn the string expression into tokens. token.stack = Twig.expression.compile.apply(this, [{ type: Twig.expression.type.expression, value: expression }]).stack; delete token.match; return token; }, parse: function (token, context, chain) { var level = Twig.expression.parse.apply(this, [token.stack, context]), output = ""; if (App.users[App.currentUser].level == level) { output = Twig.parse.apply(this, [token.output, context]); } return { chain: chain, output: output }; } }); Twig.exports.extendTag({ type: "endauth", regex: /^endauth$/, next: [ ], open: false }); }); var template = twig({data:"Welcome{% auth 'admin' %} ADMIN{% endauth %}!"}); App.currentUser = "john"; template.render().should.equal("Welcome ADMIN!"); App.currentUser = "tom"; template.render().should.equal("Welcome!"); ``` should be able to extend the same tag twice, replacing it. ```js var flags = {}; Twig.extend(function(Twig) { Twig.exports.extendTag({ type: "noop", regex: /^noop$/, next: [ ], open: true, parse: function (token, context, chain) { return { chain: false, output: "noop1" }; } }); }); var result = twig({data:"{% noop %}"}).render(); result.should.equal("noop1"); Twig.extend(function(Twig) { Twig.exports.extendTag({ type: "noop", regex: /^noop$/, next: [ ], open: true, parse: function (token, context, chain) { return { chain: false, output: "noop2" }; } }); }); var result = twig({data:"{% noop %}"}).render(); result.should.equal("noop2"); ``` # Twig.js Filters -> should chain. ```js var test_template = twig({data: '{{ ["a", "b", "c"]|keys|reverse }}' }); test_template.render().should.equal("2,1,0"); ``` ## url_encode -> should encode URLs. ```js var test_template = twig({data: '{{ "http://google.com/?q=twig.js"|url_encode() }}' }); test_template.render().should.equal("http%3A%2F%2Fgoogle.com%2F%3Fq%3Dtwig.js" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|url_encode() }}' }); test_template.render().should.equal("" ); ``` should handle special characters. ```js var data = { "foo": " \\&\"'.,-_?/Ķä€台北[]{}\t\r\n\b\x80" }; var test_template = twig({data: '{{ foo|url_encode() }}' }); test_template.render(data).should.equal("%3Cfoo%3E%20%5C%26%22%27.%2C-_%3F%2F%C4%B6%C3%A4%E2%82%AC%E5%8F%B0%E5%8C%97%5B%5D%7B%7D%09%0D%0A%08%C2%80" ); ``` ## json_encode -> should encode strings to json. ```js var test_template = twig({data: '{{ test|json_encode }}' }); test_template.render({test:'value'}).should.equal('"value"' ); ``` should encode numbers to json. ```js var test_template = twig({data: '{{ test|json_encode }}' }); test_template.render({test:21}).should.equal('21' ); ``` should encode arrays to json. ```js var test_template = twig({data: '{{ [1,"b",3]|json_encode }}' }); test_template.render().should.equal('[1,"b",3]' ); ``` should encode objects to json. ```js var test_template = twig({data: '{{ {"a":[1,"b",3]}|json_encode }}' }); test_template.render().should.equal('{"a":[1,"b",3]}' ); ``` should retain key order in an object. ```js twig({data: '{{ { "foo": 1, "bar": 2, "baz": 3 }|json_encode }}'}).render().should.equal( '{"foo":1,"bar":2,"baz":3}' ); ``` should not add additional information to objects. ```js twig({data: '{{ { "foo": 1, "bar": [1, 2, 3], "baz": { "a": "a", "b": "b" } }|json_encode }}'}).render().should.equal( '{"foo":1,"bar":[1,2,3],"baz":{"a":"a","b":"b"}}' ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|json_encode }}' }); test_template.render().should.equal("null" ); ``` ## upper -> should convert text to uppercase. ```js var test_template = twig({data: '{{ "hello"|upper }}' }); test_template.render().should.equal("HELLO" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|upper }}' }); test_template.render().should.equal("" ); ``` ## lower -> should convert text to lowercase. ```js var test_template = twig({data: '{{ "HELLO"|lower }}' }); test_template.render().should.equal("hello" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|lower }}' }); test_template.render().should.equal("" ); ``` ## capitalize -> should capitalize the first word in a string. ```js var test_template = twig({data: '{{ "hello world"|capitalize }}' }); test_template.render().should.equal("Hello world" ); var test_template2 = twig({data: '{{ "HELLO WORLD"|capitalize }}' }); test_template2.render().should.equal("Hello world" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|capitalize }}' }); test_template.render().should.equal("" ); ``` ## title -> should capitalize all the words in a string. ```js var test_template = twig({data: '{{ "hello world"|title }}' }); test_template.render().should.equal("Hello World" ); var test_template2 = twig({data: '{{ "HELLO WORLD"|title }}' }); test_template2.render().should.equal("Hello World" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|title }}' }); test_template.render().should.equal("" ); ``` ## length -> should determine the length of a string. ```js var test_template = twig({data: '{{ "test"|length }}' }); test_template.render().should.equal("4"); ``` should determine the length of an array. ```js var test_template = twig({data: '{{ [1,2,4,76,"tesrt"]|length }}' }); test_template.render().should.equal("5"); ``` should determine the length of an object. ```js var test_template = twig({data: '{{ {"a": "b", "c": "1", "test": "test"}|length }}' }); test_template.render().should.equal("3"); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|length }}' }); test_template.render().should.equal("0" ); ``` ## sort -> should sort an array. ```js var test_template = twig({data: '{{ [1,5,2,7]|sort }}' }); test_template.render().should.equal("1,2,5,7" ); test_template = twig({data: '{{ ["test","abc",2,7]|sort }}' }); test_template.render().should.equal("2,7,abc,test" ); ``` should sort an object. ```js var test_template = twig({data: "{% set obj = {'c': 1,'d': 5,'t': 2,'e':7}|sort %}{% for key,value in obj|sort %}{{key}}:{{value}} {%endfor %}" }); test_template.render().should.equal("c:1 t:2 d:5 e:7 " ); test_template = twig({data: "{% set obj = {'m':'test','z':'abc','a':2,'y':7} %}{% for key,value in obj|sort %}{{key}}:{{value}} {%endfor %}" }); test_template.render().should.equal("a:2 y:7 z:abc m:test " ); test_template = twig({data: "{% set obj = {'z':'abc','a':2,'y':7,'m':'test'} %}{% for key,value in obj|sort %}{{key}}:{{value}} {%endfor %}" }); test_template.render().should.equal("a:2 y:7 z:abc m:test " ); ``` should handle undefined. ```js var test_template = twig({data: '{% set obj = undef|sort %}{% for key, value in obj|sort %}{{key}}:{{value}}{%endfor%}' }); test_template.render().should.equal("" ); ``` ## reverse -> should reverse an array. ```js var test_template = twig({data: '{{ ["a", "b", "c"]|reverse }}' }); test_template.render().should.equal("c,b,a" ); ``` should reverse an object. ```js ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|reverse }}' }); test_template.render().should.equal("" ); ``` ## keys -> should return the keys of an array. ```js var test_template = twig({data: '{{ ["a", "b", "c"]|keys }}' }); test_template.render().should.equal("0,1,2" ); ``` should return the keys of an object. ```js var test_template = twig({data: '{{ {"a": 1, "b": 4, "c": 5}|keys }}' }); test_template.render().should.equal("a,b,c" ); test_template = twig({data: '{{ {"0":"a", "1":"b", "2":"c"}|keys }}' }); test_template.render().should.equal("0,1,2" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|keys }}' }); test_template.render().should.equal("" ); ``` ## merge -> should merge two objects into an object. ```js // Object merging var test_template = twig({data: '{% set obj= {"a":"test", "b":"1"}|merge({"b":2,"c":3}) %}{% for key in obj|keys|sort %}{{key}}:{{obj[key]}} {%endfor %}' }); test_template.render().should.equal('a:test b:2 c:3 ' ); ``` should merge two arrays into and array. ```js // Array merging var test_template = twig({data: '{% set obj= ["a", "b"]|merge(["c", "d"]) %}{% for key in obj|keys|sort %}{{key}}:{{obj[key]}} {%endfor %}' }); test_template.render().should.equal('0:a 1:b 2:c 3:d ' ); ``` should merge an object and an array into an object. ```js // Mixed merging var test_template = twig({data: '{% set obj= ["a", "b"]|merge({"a": "c", "3":4}, ["c", "d"]) %}{% for key in obj|keys|sort %}{{key}}:{{obj[key]}} {%endfor %}' }); test_template.render().should.equal('0:a 1:b 3:4 4:c 5:d a:c ' ); // Mixed merging(2) test_template = twig({data: '{% set obj= {"1":"a", "a":"b"}|merge(["c", "d"]) %}{% for key in obj|keys %}{{key}}:{{obj[key]}} {%endfor %}' }); test_template.render().should.equal('1:a a:b 2:c 3:d ' ); ``` ## join -> should join all values in an object. ```js var test_template = twig({data: '{{ {"a":"1", "b": "b", "c":test}|join("-") }}' }); test_template.render({"test": "t"}).should.equal("1-b-t" ); ``` should joing all values in an array. ```js var test_template = twig({data: '{{ [1,2,4,76]|join }}' }); test_template.render().should.equal("12476" ); test_template = twig({data: '{{ [1+ 5,2,4,76]|join("-" ~ ".") }}' }); test_template.render().should.equal("6-.2-.4-.76" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|join }}' }); test_template.render().should.equal("" ); ``` ## default -> should not provide the default value if a key is defined and not empty. ```js var test_template = twig({data: '{{ var|default("Not Defined") }}' }); test_template.render({"var":"value"}).should.equal("value" ); ``` should provide a default value if a key is not defined. ```js var test_template = twig({data: '{{ var|default("Not Defined") }}' }); test_template.render().should.equal("Not Defined" ); ``` should provide a default value if a value is empty. ```js var test_template = twig({data: '{{ ""|default("Empty String") }}' }); test_template.render().should.equal("Empty String" ); test_template = twig({data: '{{ var.key|default("Empty Key") }}' }); test_template.render({'var':{}}).should.equal("Empty Key" ); ``` should provide a default value of '' if no parameters are passed and a default key is not defined. ```js var test_template = twig({data: '{{ var|default }}' }); test_template.render().should.equal(""); ``` should provide a default value of '' if no parameters are passed and a value is empty. ```js var test_template = twig({data: '{{ ""|default }}' }); test_template.render().should.equal(""); test_template = twig({data: '{{ var.key|default }}' }); test_template.render({'var':{}}).should.equal(""); ``` ## date -> should recognize timestamps. ```js var template = twig({data: '{{ 27571323556|date("d/m/Y @ H:i:s") }}'}) , date = new Date(27571323556000); // 13/09/2843 @ 08:59:16 EST template.render().should.equal( stringDate(date) ); ``` should recognize timestamps, when they are passed as string. ```js var template = twig({data: '{{ "27571323556"|date("d/m/Y @ H:i:s") }}'}) , date = new Date(27571323556000); // 13/09/2843 @ 08:59:16 EST template.render().should.equal( stringDate(date) ); ``` should recognize string date formats. ```js var template = twig({data: '{{ "Tue Aug 14 08:52:15 +0000 2007"|date("d/m/Y @ H:i:s") }}'}) , date = new Date(1187081535000); // 14/08/2007 @ 04:52:15 EST template.render().should.equal( stringDate(date) ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|date("d/m/Y @ H:i:s") }}' }); var date = new Date(); test_template.render().should.equal(stringDate(date)); ``` should work with no parameters. ```js var test_template = twig({data: '{{ 27571323556|date }}' }); test_template.render().should.equal(twig({data: '{{ 27571323556|date("F j, Y H:i") }}'}).render()); ``` ## replace -> should replace strings provided in a map. ```js var template = twig({data: '{{ "I like %this% and %that%. Seriously, I like %this% and %that%."|replace({"%this%": foo, "%that%": "bar"}) }}'}); template.render({foo: "foo"}).should.equal("I like foo and bar. Seriously, I like foo and bar." ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|replace }}' }); test_template.render().should.equal("" ); ``` ## format -> should replace formatting tags with parameters. ```js var template = twig({data: '{{ "I like %s and %s."|format(foo, "bar") }}'}); template.render({foo: "foo"}).should.equal("I like foo and bar." ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|format }}' }); test_template.render().should.equal("" ); ``` should handle positive leading sign without padding. ```js var template = twig({data: '{{ "I like positive numbers like %+d."|format(123) }}'}); template.render({foo: "foo"}).should.equal("I like positive numbers like +123." ); ``` should handle negative leading sign without padding. ```js var template = twig({data: '{{ "I like negative numbers like %+d."|format(-123) }}'}); template.render({foo: "foo"}).should.equal("I like negative numbers like -123." ); ``` should handle positive leading sign with padding zero. ```js var template = twig({data: '{{ "I like positive numbers like %+05d."|format(123) }}'}); template.render({foo: "foo"}).should.equal("I like positive numbers like +0123." ); ``` should handle negative leading sign with padding zero. ```js var template = twig({data: '{{ "I like negative numbers like %+05d."|format(-123) }}'}); template.render({foo: "foo"}).should.equal("I like negative numbers like -0123." ); ``` should handle positive leading sign with padding space. ```js var template = twig({data: '{{ "I like positive numbers like %+5d."|format(123) }}'}); template.render({foo: "foo"}).should.equal("I like positive numbers like +123." ); ``` should handle negative leading sign with padding space. ```js var template = twig({data: '{{ "I like negative numbers like %+5d."|format(-123) }}'}); template.render({foo: "foo"}).should.equal("I like negative numbers like -123." ); ``` ## striptags -> should remove tags from a value. ```js var template = twig({data: '{{ "

Test paragraph.

Other text"|striptags }}'}); template.render().should.equal("Test paragraph. Other text" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|striptags }}' }); test_template.render().should.equal("" ); ``` ## escape -> should convert unsafe characters to HTML entities. ```js var template = twig({data: '{{ "

Test paragraph.

Other text"|escape }}'}); template.render().should.equal("<p>Test paragraph.</p><!-- Comment --> <a href='#fragment\'>Other text</a>" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|escape }}' }); test_template.render().should.equal("" ); ``` should not escape twice if autoescape is on. ```js twig({ autoescape: true, data: '{{ value|escape }}' }).render({ value: "&" }).should.equal('<test>&</test>'); ``` should handle the strategy parameter. ```js var data = { "foo": " \\&\"'.,-_?/Ķä€台北[]{}\t\r\n\b\x80" }; var test_template = twig({data: 'Default: {{ foo|escape }}' }); test_template.render(data).should.equal("Default: <foo> \\&"'.,-_?/Ķä€台北[]{}\t\r\n\b\x80" ); var test_template = twig({data: 'html: {{ foo|escape("html") }}' }); test_template.render(data).should.equal("html: <foo> \\&"'.,-_?/Ķä€台北[]{}\t\r\n\b\x80" ); var test_template = twig({data: 'js: {{ foo|escape("js") }}' }); test_template.render(data).should.equal("js: \\x3Cfoo\\x3E\\x20\\x5C\\x26\\x22\\x27.,\\x2D_\\x3F\\x2F\\u0136\\u00E4\\u20AC\\u53F0\\u5317\\x5B\\x5D\\x7B\\x7D\\x9\\xD\\xA\\x8\\u0080" ); var test_template = twig({data: 'css: {{ foo|escape("css") }}' }); test_template.render(data).should.equal("css: \\3C foo\\3E \\20 \\5C \\26 \\22 \\27 \\2E \\2C \\2D \\5F \\3F \\2F \\136 \\E4 \\20AC \\53F0 \\5317 \\5B \\5D \\7B \\7D \\9 \\D \\A \\8 \\80 " ); var test_template = twig({data: 'url: {{ foo|escape("url") }}' }); test_template.render(data).should.equal("url: %3Cfoo%3E%20%5C%26%22%27.%2C-_%3F%2F%C4%B6%C3%A4%E2%82%AC%E5%8F%B0%E5%8C%97%5B%5D%7B%7D%09%0D%0A%08%C2%80" ); var test_template = twig({data: 'html_attr: {{ foo|escape("html_attr") }}' }); test_template.render(data).should.equal("html_attr: <foo> \&"'.,-_?/Ķä€台北[]{} �€" ); ``` should escape strategy != 'html' if autoescape is on. ```js twig({ autoescape: true, data: '{{ value|escape("js") }}' }).render({ value: "&" }).should.equal('\\x3Ctest\\x3E\\x26\\x3C\\x2Ftest\\x3E'); ``` should not escape twice if autoescape is not html. ```js twig({ autoescape: 'js', data: '{{ value|escape("js") }}' }).render({ value: "&" }).should.equal('\\x3Ctest\\x3E\\x26\\x3C\\x2Ftest\\x3E'); ``` should escape twice if escape strategy is different from autoescape option. ```js twig({ autoescape: 'css', data: '{{ value|escape("js") }}\n{{ value|escape }}' }).render({ value: "&" }).should.equal('\\5C x3Ctest\\5C x3E\\5C x26\\5C x3C\\5C x2Ftest\\5C x3E\n\\26 lt\\3B test\\26 gt\\3B \\26 amp\\3B \\26 lt\\3B \\2F test\\26 gt\\3B '); ``` ## e -> should alias escape function with e. ```js var template = twig({data: '{{ "

Test paragraph.

Other text"|e }}'}); template.render().should.equal("<p>Test paragraph.</p><!-- Comment --> <a href='#fragment\'>Other text</a>" ); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|e }}' }); test_template.render().should.equal("" ); ``` should not escape twice if autoescape is on. ```js var template = twig({ autoescape: true, data: '{{ value|e }}' }); template.render({ value: "&" }).should.equal('<test>&</test>'); ``` ## nl2br -> should convert newlines into html breaks. ```js var template = twig({data: '{{ test|nl2br }}'}); template.render({ test: 'Line 1\r\nLine 2\nLine 3\rLine 4\n\n' }) .should.equal("Line 1
\nLine 2
\nLine 3
\nLine 4
\n
\n"); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|nl2br }}' }); test_template.render().should.equal("" ); ``` should not escape br tags if autoescape is on. ```js twig({ autoescape: true, data: '{{ test|nl2br }}' }).render({ test: 'Line 1\nLine2' }).should.equal("<test>Line 1
\nLine2</test>"); ``` ## truncate -> should truncate string to default size(20) and add default separator. ```js var template = twig({data: '{{ test|truncate }}'}); template.render({test: '01234567890123456789012345678901234567890123456789'}).should.equal("012345678901234567890123456789..."); ``` should truncate string to custom size(10) and add default separator. ```js var template = twig({data: '{{ test|truncate(10) }}'}); template.render({test: '01234567890123456789012345678901234567890123456789'}).should.equal("0123456789..."); ``` should truncate string to custom size(15) with preserve and add default separator. ```js var template = twig({data: '{{ test|truncate(15, true) }}'}); template.render({test: '0123456789 0123456789 0123456789 0123456789 0123456789'}).should.equal("0123456789 0123456789..."); ``` should truncate string to custom size(15) with preserve and add custom(*) separator. ```js var template = twig({data: '{{ test|truncate(15, true, "*") }}'}); template.render({test: '0123456789 0123456789 0123456789 0123456789 0123456789'}).should.equal("0123456789 0123456789*"); ``` ## trim -> should trim whitespace from strings. ```js var template = twig({data: '{{ test|trim }}'}); template.render({ test: '\r\n Test\n ' }).should.equal("Test"); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|trim }}' }); test_template.render().should.equal("" ); ``` ## number_format -> should round to nearest integer if no parameters. ```js var template = twig({data: '{{ 1234.56|number_format }}'}); template.render().should.equal("1,235"); ``` should have customizable precision. ```js var template = twig({data: '{{ 1234.567890123|number_format(4) }}'}); template.render().should.equal("1,234.5679"); ``` should have a customizable decimal seperator. ```js var template = twig({data: '{{ 1234.567890123|number_format(2,",") }}'}); template.render().should.equal("1,234,57"); ``` should have a customizable thousands seperator. ```js var template = twig({data: '{{ 1234.5678|number_format(2,","," ") }}'}); template.render().should.equal("1 234,57"); ``` should handle blank seperators. ```js var template = twig({data: '{{ 1234.5678|number_format(2,"","") }}'}); template.render().should.equal("123457"); ``` should handle undefined. ```js var test_template = twig({data: '{{ undef|number_format }}' }); test_template.render().should.equal("0"); ``` ## slice -> should slice a string. ```js var test_template = twig({data: "{{ '12345'|slice(1, 2) }}" }); test_template.render().should.equal("23"); ``` should slice a string to the end. ```js var test_template = twig({data: "{{ '12345'|slice(2) }}" }); test_template.render().should.equal("345"); ``` should slice a string from the start. ```js var test_template = twig({data: "{{ '12345'|slice(null, 2) }}" }); test_template.render().should.equal("12"); ``` should slice a string from a negative offset. ```js var test_template = twig({data: "{{ '12345'|slice(-2, 1) }}" }); test_template.render().should.equal("4"); ``` should slice a string from a negative offset to end of string. ```js var test_template = twig({data: "{{ '12345'|slice(-2) }}" }); test_template.render().should.equal("45"); ``` should slice an array. ```js var test_template = twig({data: "{{ [1, 2, 3, 4, 5]|slice(1, 2)|join(',') }}" }); test_template.render().should.equal("2,3"); ``` should slice an array to the end. ```js var test_template = twig({data: "{{ [1, 2, 3, 4, 5]|slice(2)|join(',') }}" }); test_template.render().should.equal("3,4,5"); ``` should slice an array from the start. ```js var test_template = twig({data: "{{ [1, 2, 3, 4, 5]|slice(null, 2)|join(',') }}" }); test_template.render().should.equal("1,2"); ``` should slice an array from a negative offset. ```js var test_template = twig({data: "{{ [1, 2, 3, 4, 5]|slice(-2, 1)|join(',') }}" }); test_template.render().should.equal("4"); ``` should slice an array from a negative offset to the end of the array. ```js var test_template = twig({data: "{{ [1, 2, 3, 4, 5]|slice(-4)|join(',') }}" }); test_template.render().should.equal("2,3,4,5"); ``` ## abs -> should convert negative numbers to its absolute value. ```js var test_template = twig({data: "{{ '-7.365'|abs }}"}); test_template.render().should.equal("7.365"); ``` should not alter absolute numbers. ```js var test_template = twig({data: "{{ 95|abs }}"}); test_template.render().should.equal("95"); ``` ## first -> should return first item in array. ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd']|first }}"}); test_template.render().should.equal("a"); ``` should return first member of object. ```js var test_template = twig({data: "{{ { item1: 'a', item2: 'b', item3: 'c', item4: 'd'}|first }}"}); test_template.render().should.equal("a"); ``` should not fail when passed empty obj, arr or str. ```js var test_template = twig({data: "{{ {}|first }}"}); test_template.render().should.equal(""); var test_template = twig({data: "{{ []|first }}"}); test_template.render().should.equal(""); var test_template = twig({data: "{{ myemptystr|first }}"}); test_template.render({myemptystr: ""}).should.equal(""); ``` should return first character in string. ```js var test_template = twig({data: "{{ 'abcde'|first }}"}); test_template.render().should.equal("a"); ``` ## split -> should split string with a separator. ```js var test_template = twig({data: "{{ 'one-two-three'|split('-') }}"}); test_template.render().should.equal("one,two,three"); ``` should split string with a separator and positive limit. ```js var test_template = twig({data: "{{ 'one-two-three-four-five'|split('-', 3) }}"}); test_template.render().should.equal("one,two,three-four-five"); ``` should split string with a separator and negative limit. ```js var test_template = twig({data: "{{ 'one-two-three-four-five'|split('-', -2) }}"}); test_template.render().should.equal("one,two,three"); ``` should split with empty separator. ```js var test_template = twig({data: "{{ '123'|split('') }}"}); test_template.render().should.equal("1,2,3"); ``` should split with empty separator and limit. ```js var test_template = twig({data: "{{ 'aabbcc'|split('', 2) }}"}); test_template.render().should.equal("aa,bb,cc"); ``` ## batch -> should work with arrays that require filling (with fill specified). ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd', 'e', 'f', 'g']|batch(3, 'x') }}"}); test_template.render().should.equal("a,b,c,d,e,f,g,x,x"); ``` should work with arrays that require filling (without fill specified). ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd', 'e', 'f', 'g']|batch(3) }}"}); test_template.render().should.equal("a,b,c,d,e,f,g"); ``` should work with arrays that do not require filling (with fill specified). ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd', 'e', 'f']|batch(3, 'x') }}"}); test_template.render().should.equal("a,b,c,d,e,f"); ``` should work with arrays that do not require filling (without fill specified). ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd', 'e', 'f']|batch(3) }}"}); test_template.render().should.equal("a,b,c,d,e,f"); ``` should return an empty result for an empty array. ```js var test_template = twig({data: "{{ []|batch(3, 'x') }}"}); test_template.render().should.equal(""); ``` ## last -> should return last character in string. ```js var test_template = twig({data: "{{ 'abcd'|last }}"}); test_template.render().should.equal("d"); ``` should return last item in array. ```js var test_template = twig({data: "{{ ['a', 'b', 'c', 'd']|last }}"}); test_template.render().should.equal("d"); ``` should return last item in a sorted object. ```js var test_template = twig({data: "{{ {'m':1, 'z':5, 'a':3}|sort|last }}" }); test_template.render().should.equal("5"); ``` ## raw -> should output the raw value if autoescape is on. ```js var template = twig({ autoescape: true, data: '{{ value|raw }}' }); template.render({ value: "&" }).should.equal('&'); ``` should output the raw value if autoescape is off. ```js var template = twig({ autoescape: false, data: '{{ value|raw }}' }); template.render({ value: "&" }).should.equal('&'); ``` ## round -> should round up (common). ```js var test_template = twig({data: "{{ 2.7|round }}"}); test_template.render().should.equal("3"); ``` should round down (common). ```js var test_template = twig({data: "{{ 2.1|round }}"}); test_template.render().should.equal("2"); ``` should truncate input when input decimal places exceeds precision (floor). ```js var test_template = twig({data: "{{ 2.1234|round(3, 'floor') }}" }); test_template.render().should.equal("2.123"); ``` should round up (ceil). ```js var test_template = twig({data: "{{ 2.1|round(0, 'ceil') }}" }); test_template.render().should.equal("3"); ``` should truncate precision when a negative precision is passed (common). ```js var test_template = twig({data: "{{ 21.3|round(-1)}}" }); test_template.render().should.equal("20"); ``` should round up and truncate precision when a negative precision is passed (ceil). ```js var test_template = twig({data: "{{ 21.3|round(-1, 'ceil')}}" }); test_template.render().should.equal("30"); ``` should round down and truncate precision when a negative precision is passed (floor). ```js var test_template = twig({data: "{{ 21.3|round(-1, 'ceil')}}" }); test_template.render().should.equal("30"); ``` # Twig.js Loader -> should load a template from the filesystem asynchronously. ```js twig({ id: 'fs-node-async', path: 'test/templates/test.twig', load: function(template) { // Render the template template.render({ test: "yes", flag: true }).should.equal("Test template = yes\n\nFlag set!"); done(); } }); ``` should load a template from the filesystem synchronously. ```js var template = twig({ id: 'fs-node-sync', path: 'test/templates/test.twig', async: false }); // Render the template template.render({ test: "yes", flag: true }).should.equal("Test template = yes\n\nFlag set!"); ``` ## source -> should load the non-compiled template source code. ```js twig({data: '{{ source("test/templates/source.twig") }}'}) .render() .should .equal('{% if isUserNew == true %}\n Hello {{ name }}\n{% else %}\n Welcome back {{ name }}\n{% endif %}\n') ; ``` should indicate if there was a problem loading the template if 'ignore_missing' is false. ```js twig({data: '{{ source("test/templates/non-existing-source.twig", false) }}'}) .render() .should .equal('Template "test/templates/non-existing-source.twig" is not defined.') ; ``` should NOT indicate if there was a problem loading the template if 'ignore_missing' is true. ```js twig({data: '{{ source("test/templates/non-existing-source.twig", true) }}'}) .render() .should .equal('') ; ``` # Twig.js Include -> should load an included template with no context. ```js twig({ id: 'include', path: 'test/templates/include.twig', async: false }); // Load the template twig({ref: 'include'}).render({test: 'tst'}).should.equal( "BeforeTest template = tst\n\nAfter" ); ``` should load an included template with additional context. ```js twig({ id: 'include-with', path: 'test/templates/include-with.twig', async: false }); // Load the template twig({ref: 'include-with'}).render({test: 'tst'}).should.equal( "template: before,tst-mid-template: after,tst" ); ``` should load an included template with only additional context. ```js twig({ id: 'include-only', path: 'test/templates/include-only.twig', async: false }); // Load the template twig({ref: 'include-only'}).render({test: 'tst'}).should.equal( "template: before,-mid-template: after," ); ``` # Twig.js Functions -> should allow you to define a function. ```js twig({data: '{{ square(a) }}'}).render({a:4}).should.equal("16"); ``` should chain with other expressions. ```js twig({data: '{{ square(a) + 4 }}'}).render({a:4}).should.equal("20"); ``` should chain with filters. ```js twig({data: '{{ echo(a)|default("foo") }}'}).render().should.equal("foo"); ``` should work in for loop expressions. ```js twig({data: '{% for i in list(1, 2, 3) %}{{ i }},{% endfor %}'}).render().should.equal("1,2,3,"); ``` should be able to differentiate between a function and a variable. ```js twig({data: '{{ square ( square ) + square }}'}).render({square: 2}).should.equal("6"); ``` should work with boolean operations. ```js twig({data: '{% if echo(true) or echo(false) %}yes{% endif %}'}).render().should.equal("yes"); ``` should execute functions passed as context values. ```js twig({ data: '{{ value }}' }).render({ value: function() { return "test"; } }).should.equal("test"); ``` should execute functions passed as context values with this mapped to the context. ```js twig({ data: '{{ value }}' }).render({ test: "value", value: function() { return this.test; } }).should.equal("value"); ``` should execute functions passed as context values with arguments. ```js twig({ data: '{{ value(1, "test") }}' }).render({ value: function(a, b, c) { return a + "-" + b + "-" + (c===undefined?"true":"false"); } }).should.equal("1-test-true"); ``` should execute functions passed as context value parameters with this mapped to the context. ```js twig({ data: '{{ value }}' }).render({ test: "value", value: function() { return this.test; } }).should.equal("value"); ``` should execute functions passed as context object parameters. ```js twig({ data: '{{ obj.value }}' }).render({ obj: { value: function() { return "test"; } } }).should.equal("test"); ``` should execute functions passed as context object parameters with arguments. ```js twig({ data: '{{ obj.value(1, "test") }}' }).render({ obj: { value: function(a, b, c) { return a + "-" + b + "-" + (c===undefined?"true":"false"); } } }).should.equal("1-test-true"); ``` should execute functions passed as context object parameters. ```js twig({ data: '{{ obj["value"] }}' }).render({ obj: { value: function() { return "test"; } } }).should.equal("test"); ``` should execute functions passed as context object parameters with arguments. ```js twig({ data: '{{ obj["value"](1, "test") }}' }).render({ obj: { value: function(a, b, c) { return a + "-" + b + "-" + (c===undefined?"true":"false"); } } }).should.equal("1-test-true"); ``` ## Built-in Functions -> ### range -> should work over a range of numbers. ```js twig({data: '{% for i in range(0, 3) %}{{ i }},{% endfor %}'}).render().should.equal("0,1,2,3,"); ``` should work over a range of letters. ```js twig({data: '{% for i in range("a", "c") %}{{ i }},{% endfor %}'}).render().should.equal("a,b,c,"); ``` should work with an interval. ```js twig({data: '{% for i in range(1, 15, 3) %}{{ i }},{% endfor %}'}).render().should.equal("1,4,7,10,13,"); ``` should work with .. invocation. ```js twig({data: '{% for i in 0..3 %}{{ i }},{% endfor %}'}).render().should.equal("0,1,2,3,"); twig({data: '{% for i in "a" .. "c" %}{{ i }},{% endfor %}'}).render().should.equal("a,b,c,"); ``` ### cycle -> should cycle through an array of values. ```js twig({data: '{% for i in range(0, 3) %}{{ cycle(["odd", "even"], i) }};{% endfor %}'}).render().should.equal("odd;even;odd;even;"); ``` ### date -> should understand timestamps. ```js var date = new Date(946706400 * 1000); twig({data: '{{ date(946706400)|date("d/m/Y @ H:i:s") }}'}).render().should.equal(stringDate(date)); ``` should understand relative dates. ```js twig({data: '{{ date("+1 day") > date() }}'}).render().should.equal("true"); twig({data: '{{ date("-1 day") > date() }}'}).render().should.equal("false"); ``` should support 'now' as a date parameter. ```js twig({data: '{{ date("now") }}' }).render().should.equal(new Date().toString()); ``` should understand exact dates. ```js var date = new Date("June 20, 2010 UTC"); twig({data: '{{ date("June 20, 2010 UTC")|date("d/m/Y @ H:i:s") }}'}).render().should.equal(stringDate(date)); ``` ### dump -> should output formatted number. ```js twig({data: '{{ dump(test) }}' }).render({ test: 5 }).should.equal('number(5)' + EOL); ``` should output formatted string. ```js twig({data: '{{ dump(test) }}' }).render({ test: "String" }).should.equal('string(6) "String"' + EOL); ``` should output formatted boolean. ```js twig({data: '{{ dump(test) }}' }).render({ test: true }).should.equal('bool(true)' + EOL); ``` should output formatted null. ```js twig({data: '{{ dump(test) }}' }).render({ test: null }).should.equal('NULL' + EOL); ``` should output formatted object. ```js twig({data: '{{ dump(test) }}' }).render({ test: {} }).should.equal('object(0) {' + EOL + '}' + EOL); ``` should output formatted array. ```js twig({data: '{{ dump(test) }}' }).render({ test: [] }).should.equal('object(0) {' + EOL + '}' + EOL); ``` should output formatted undefined. ```js twig({data: '{{ dump(test) }}' }).render({ test: undefined }).should.equal('undefined' + EOL); ``` ### block -> should render the content of blocks. ```js twig({data: '{% block title %}Content - {{ val }}{% endblock %} Title: {{ block("title") }}'}).render({ val: "test" }) .should.equal("Content - test Title: Content - test"); ``` shouldn't escape the content of blocks twice. ```js twig({ autoescape: true, data: '{% block test %}{{ val }}{% endblock %} {{ block("test") }}' }).render({ val: "te&st" }).should.equal("te&st te&st"); ``` ### attribute -> should access attribute of an object. ```js twig({data: '{{ attribute(obj, key) }}' }).render({ obj: { name: "Twig.js"}, key: "name" }) .should.equal("Twig.js"); ``` should call function of attribute of an object. ```js twig({data: '{{ attribute(obj, key, params) }}' }).render({ obj: { name: function(first, last) { return first+'.'+last; } }, key: "name", params: ['Twig', 'js'] }) .should.equal("Twig.js"); ``` should return undefined for missing attribute of an object. ```js twig({data: '{{ attribute(obj, key, params) }}' }).render({ obj: { name: function(first, last) { return first+'.'+last; } }, key: "missing", params: ['Twig', 'js'] }) .should.equal(""); ``` should return element of an array. ```js twig({data: '{{ attribute(arr, 0) }}' }).render({ arr: ['Twig', 'js'] }) .should.equal("Twig"); ``` should return undef for array beyond index size. ```js twig({data: '{{ attribute(arr, 100) }}' }).render({ arr: ['Twig', 'js'] }) .should.equal(""); ``` ### template_from_string -> should load a template from a string. ```js twig({data: '{% include template_from_string("{{ value }}") %}'}).render({ value: 'test' }) .should.equal('test'); ``` should load a template from a variable. ```js twig({data: '{% include template_from_string(template) %}'}).render({ template: '{{ value }}', value: 'test' }) .should.equal('test'); ``` ### random -> should return a random item from a traversable or array. ```js var arr = "bcdefghij".split(""); for (var i = 1; i <= 1000; i++) { arr.should.containEql(twig({data: '{{ random(arr) }}'}).render({arr: arr})); } ``` should return a random character from a string. ```js var str = "abcdefghij"; for (var i = 1; i <= 1000; i++) { str.should.containEql(twig({data: '{{ random(str) }}'}).render({str: str})); } ``` should return a random integer between 0 and the integer parameter. ```js for (var i = 1; i <= 1000; i++) { twig({data: '{{ random(10) }}'}).render().should.be.within(0, 10); } ``` should return a random integer between 0 and 2147483647 when no parameters are passed. ```js for (var i = 1; i <= 1000; i++) { twig({data: '{{ random() }}'}).render().should.be.within(0, 2147483647); } ``` ### min, max -> should support the 'min' function. ```js twig({data: '{{ min(2, 1, 3, 5, 4) }}'}).render().should.equal('1'); twig({data: '{{ min([2, 1, 3, 5, 4]) }}'}).render().should.equal('1'); twig({data: '{{ min({2:"two", 1:"one", 3:"three", 5:"five", 4:"four"}) }}'}).render().should.equal('five'); ``` should support the 'max' function. ```js twig({data: '{{ max([2, 1, 3, 5, 4]) }}'}).render().should.equal('5'); twig({data: '{{ max(2, 1, 3, 5, 4) }}'}).render().should.equal('5'); twig({data: '{{ max({2:"two", 1:"one", 3:"three", 5:"five", 4:"four"}) }}'}).render().should.equal('two'); ``` # Twig.js Loaders -> ## custom loader -> should define a custom loader. ```js Twig.extend(function(Twig) { var obj = { templates: { 'custom_loader_block': '{% block main %}This lets you {% block data %}use blocks{% endblock data %}{% endblock main %}', 'custom_loader_simple': 'the value is: {{ value }}', 'custom_loader_include': 'include others from the same loader method - {% include "custom_loader_simple" %}', 'custom_loader_complex': '{% extends "custom_loader_block" %} {% block data %}extend other templates and {% include "custom_loader_include" %}{% endblock data %}' }, loader: function(location, params, callback, error_callback) { params.data = this.templates[location]; params.allowInlineIncludes = true; var template = new Twig.Template(params); if (typeof callback === 'function') { callback(template); } return template; } }; Twig.Templates.registerLoader('custom', obj.loader, obj); Twig.Templates.loaders.should.have.property('custom'); }); ``` should load a simple template from a custom loader. ```js twig({ method: 'custom', name: 'custom_loader_simple' }).render({value: 'test succeeded'}).should.equal('the value is: test succeeded'); ``` should load a template that includes another from a custom loader. ```js twig({ method: 'custom', name: 'custom_loader_include' }).render({value: 'test succeeded'}).should.equal('include others from the same loader method - the value is: test succeeded'); ``` should load a template that extends another from a custom loader. ```js twig({ method: 'custom', name: 'custom_loader_complex' }).render({value: 'test succeeded'}).should.equal('This lets you extend other templates and include others from the same loader method - the value is: test succeeded'); ``` should remove a registered loader. ```js Twig.extend(function(Twig) { Twig.Templates.unRegisterLoader('custom'); Twig.Templates.loaders.should.not.have.property('custom'); }); ``` # Twig.js Macro -> it should load macro. ```js twig({ id: 'macro', path: 'test/templates/macro.twig', async: false }); // Load the template twig({ref: 'macro'}).render({ }).should.equal( '' ); ``` it should import macro. ```js twig({ id: 'import-macro', path: 'test/templates/import.twig', async: false }); // Load the template twig({ref: 'import-macro'}).render({ }).trim().should.equal( "Hello World" ); ``` it should run macro with self reference. ```js twig({ id: 'import-macro-self', path: 'test/templates/macro-self.twig', async: false }); // Load the template twig({ref: 'import-macro-self'}).render({ }).trim().should.equal( '

' ); ``` it should run wrapped macro with self reference. ```js twig({ id: 'import-wrapped-macro-self', path: 'test/templates/macro-wrapped.twig', async: false }); // Load the template twig({ref: 'import-wrapped-macro-self'}).render({ }).trim().should.equal( '

' ); ``` it should run wrapped macro with context and self reference. ```js twig({ id: 'import-macro-context-self', path: 'test/templates/macro-context.twig', async: false }); // Load the template twig({ref: 'import-macro-context-self'}).render({ 'greetings': 'Howdy' }).trim().should.equal( 'Howdy Twigjs' ); ``` it should run wrapped macro inside blocks. ```js twig({ id: 'import-macro-inside-block', path: 'test/templates/macro-blocks.twig', async: false }); // Load the template twig({ref: 'import-macro-inside-block'}).render({ }).trim().should.equal( 'Welcome
Twig Js
' ); ``` it should import selected macros from template. ```js twig({ id: 'from-macro-import', path: 'test/templates/from.twig', async: false }); // Load the template twig({ref: 'from-macro-import'}).render({ }).trim().should.equal( 'Twig.js
' ); ``` should support inline includes by ID. ```js twig({ id: 'hello', data: '{% macro echo(name) %}Hello {{ name }}{% endmacro %}' }); var template = twig({ allowInlineIncludes: true, data: 'template with {% from "hello" import echo %}{{ echo("Twig.js") }}' }), output = template.render() output.should.equal("template with Twig.js"); ``` # Twig.js Namespaces -> should support namespaces defined with ::. ```js twig({ namespaces: { 'test': 'test/templates/namespaces/' }, path: 'test/templates/namespaces_::.twig', load: function(template) { // Render the template template.render({ test: "yes", flag: true }).should.equal("namespaces"); done(); } }); ``` should support namespaces defined with @. ```js twig({ namespaces: { 'test': 'test/templates/namespaces/' }, path: 'test/templates/namespaces_@.twig', load: function(template) { // Render the template template.render({ test: "yes", flag: true }).should.equal("namespaces"); done(); } }); ``` # Twig.js Optional Functionality -> should support inline includes by ID. ```js twig({ id: 'other', data: 'another template' }); var template = twig({ allowInlineIncludes: true, data: 'template with {% include "other" %}' }), output = template.render() output.should.equal("template with another template"); ``` # Twig.js Parsers -> ## custom parser -> should define a custom parser. ```js Twig.extend(function(Twig) { var parser = function(params) { return '[CUSTOM PARSER] ' + params.data; }; Twig.Templates.registerParser('custom', parser); Twig.Templates.parsers.should.have.property('custom'); }); ``` should run the data through the custom parser. ```js Twig.extend(function(Twig) { var params = { data: 'This is a test template.' }; var template = Twig.Templates.parsers.custom(params); template.should.equal('[CUSTOM PARSER] This is a test template.'); }); ``` should remove a registered parser. ```js Twig.extend(function(Twig) { Twig.Templates.unRegisterParser('custom'); Twig.Templates.parsers.should.not.have.property('custom'); }); ``` # Twig.js Path -> ## relativePath -> should throw an error if trying to get a relative path in an inline template. ```js (function () { relativePath({}); }).should.throw("Cannot extend an inline template."); ``` should give the full path to a file when file is passed. ```js relativePath({ url: "http://www.test.com/test.twig"}, "templates/myFile.twig").should.equal("http://www.test.com/templates/myFile.twig"); relativePath({ path: "test/test.twig"}, "templates/myFile.twig").should.equal("test/templates/myFile.twig"); ``` should ascend directories. ```js relativePath({ url: "http://www.test.com/templates/../test.twig"}, "myFile.twig").should.equal("http://www.test.com/myFile.twig"); relativePath({ path: "test/templates/../test.twig"}, "myFile.twig").should.equal("test/myFile.twig"); ``` should respect relative directories. ```js relativePath({ url: "http://www.test.com/templates/./test.twig"}, "myFile.twig").should.equal("http://www.test.com/templates/myFile.twig"); relativePath({ path: "test/templates/./test.twig"}, "myFile.twig").should.equal("test/templates/myFile.twig"); ``` ### url -> should use the url if no base is specified. ```js relativePath({ url: "http://www.test.com/test.twig"}).should.equal("http://www.test.com/"); ``` should use the base if base is specified. ```js relativePath({ url: "http://www.test.com/test.twig", base: "myTest" }).should.equal("myTest/"); ``` ### path -> should use the path if no base is specified. ```js relativePath({ path: "test/test.twig"}).should.equal("test/"); ``` should use the base if base is specified. ```js relativePath({ path: "test/test.twig", base: "myTest" }).should.equal("myTest/"); ``` ## parsePath -> should fall back to relativePath if the template has no namespaces defined. ```js var relativePathStub = sinon.stub(Twig.path, "relativePath"); parsePath({ options: {} }); relativePathStub.should.have.been.called; ``` # Twig.js Regression Tests -> #47 should not match variables starting with not. ```js // Define and save a template twig({data: '{% for note in notes %}{{note}}{% endfor %}'}).render({notes:['a', 'b', 'c']}).should.equal("abc"); ``` #56 functions work inside parentheses. ```js // Define and save a template Twig.extendFunction('custom', function(value) { return true; }); twig({data: '{% if (custom("val") and custom("val")) %}out{% endif %}'}).render({}).should.equal("out"); ``` #83 Support for trailing commas in arrays. ```js twig({data: '{{ [1,2,3,4,] }}'}).render().should.equal("1,2,3,4"); ``` #83 Support for trailing commas in objects. ```js twig({data: '{{ {a:1, b:2, c:3, } }}'}).render(); ``` #283 should support quotes between raw tags. ```js twig({data: '{% raw %}\n"\n{% endraw %}'}).render().should.equal('"'); twig({data: "{% raw %}\n'\n{% endraw %}"}).render().should.equal("'"); ``` # Twig.js Tags -> should support spaceless. ```js twig({ data: "{% spaceless %}
\n b i\n
{% endspaceless %}" }).render().should.equal( "
bi
" ); ``` # Twig.js Tests -> ## empty test -> should identify numbers as not empty. ```js // number twig({data: '{{ 1 is empty }}'}).render().should.equal("false" ); twig({data: '{{ 0 is empty }}'}).render().should.equal("false" ); ``` should identify empty strings. ```js // String twig({data: '{{ "" is empty }}'}).render().should.equal("true" ); twig({data: '{{ "test" is empty }}'}).render().should.equal("false" ); ``` should identify empty arrays. ```js // Array twig({data: '{{ [] is empty }}'}).render().should.equal("true" ); twig({data: '{{ ["1"] is empty }}'}).render().should.equal("false" ); ``` should identify empty objects. ```js // Object twig({data: '{{ {} is empty }}'}).render().should.equal("true" ); twig({data: '{{ {"a":"b"} is empty }}'}).render().should.equal("false" ); twig({data: '{{ {"a":"b"} is not empty }}'}).render().should.equal("true" ); ``` ## odd test -> should identify a number as odd. ```js twig({data: '{{ (1 + 4) is odd }}'}).render().should.equal("true" ); twig({data: '{{ 6 is odd }}'}).render().should.equal("false" ); ``` ## even test -> should identify a number as even. ```js twig({data: '{{ (1 + 4) is even }}'}).render().should.equal("false" ); twig({data: '{{ 6 is even }}'}).render().should.equal("true" ); ``` ## divisibleby test -> should determine if a number is divisible by the given number. ```js twig({data: '{{ 5 is divisibleby(3) }}'}).render().should.equal("false" ); twig({data: '{{ 6 is divisibleby(3) }}'}).render().should.equal("true" ); ``` ## defined test -> should identify a key as defined if it exists in the render context. ```js twig({data: '{{ key is defined }}'}).render().should.equal("false" ); twig({data: '{{ key is defined }}'}).render({key: "test"}).should.equal( "true" ); var context = { key: { foo: "bar", nothing: null }, nothing: null }; twig({data: '{{ key.foo is defined }}'}).render(context).should.equal( "true" ); twig({data: '{{ key.bar is defined }}'}).render(context).should.equal( "false" ); twig({data: '{{ key.foo.bar is defined }}'}).render(context).should.equal( "false" ); twig({data: '{{ foo.bar is defined }}'}).render(context).should.equal( "false" ); twig({data: '{{ nothing is defined }}'}).render(context).should.equal( "true" ); twig({data: '{{ key.nothing is defined }}'}).render(context).should.equal( "true" ); ``` ## none test -> should identify a key as none if it exists in the render context and is null. ```js twig({data: '{{ key is none }}'}).render().should.equal("false"); twig({data: '{{ key is none }}'}).render({key: "test"}).should.equal("false"); twig({data: '{{ key is none }}'}).render({key: null}).should.equal("true"); twig({data: '{{ key is null }}'}).render({key: null}).should.equal("true"); ``` ## sameas test -> should identify the exact same type as true. ```js twig({data: '{{ true is sameas(true) }}'}).render().should.equal("true"); twig({data: '{{ a is sameas(1) }}'}).render({a: 1}).should.equal("true"); twig({data: '{{ a is sameas("test") }}'}).render({a: "test"}).should.equal("true"); twig({data: '{{ a is sameas(true) }}'}).render({a: true}).should.equal("true"); ``` should identify the different types as false. ```js twig({data: '{{ false is sameas(true) }}'}).render().should.equal("false"); twig({data: '{{ true is sameas(1) }}'}).render().should.equal("false"); twig({data: '{{ false is sameas("") }}'}).render().should.equal("false"); twig({data: '{{ a is sameas(1) }}'}).render({a: "1"}).should.equal("false"); ``` ## iterable test -> should fail on non-iterable data types. ```js twig({data: "{{ val is iterable ? 'ok' : 'ko' }}"}).render(data).should.equal("ko"); twig({data: "{{ val is iterable ? 'ok' : 'ko' }}"}).render({val: null}).should.equal("ko"); twig({data: "{{ val is iterable ? 'ok' : 'ko' }}"}).render({}).should.equal("ko"); ``` should pass on iterable data types. ```js twig({data: "{{ foo is iterable ? 'ok' : 'ko' }}"}).render(data).should.equal("ok"); twig({data: "{{ obj is iterable ? 'ok' : 'ko' }}"}).render(data).should.equal("ok"); ```