From 824a2d9f587ca017fc71b84d835e72f54f9c87c4 Mon Sep 17 00:00:00 2001
From: Marvin Borner
Date: Wed, 7 Nov 2018 18:02:36 +0100
Subject: Began rewrite
---
node_modules/twig/docs/tests.md | 3567 +++++++++++++++++++++++++++++++++++++++
1 file changed, 3567 insertions(+)
create mode 100644 node_modules/twig/docs/tests.md
(limited to 'node_modules/twig/docs/tests.md')
diff --git a/node_modules/twig/docs/tests.md b/node_modules/twig/docs/tests.md
new file mode 100644
index 0000000..5afa1b7
--- /dev/null
+++ b/node_modules/twig/docs/tests.md
@@ -0,0 +1,3567 @@
+# 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: "
{{ value }}
{% endblock body %}'}); +twig({ + allowInlineIncludes: true, + autoescape: true, + data: '{% extends "parent1" %}{% block body %}{{ parent() }}{% endblock %}' +}).render({ + value: "<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": "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: "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: "