diff --git a/specification/comments.yml b/specification/comments.yml index 7b14c7f..3bad09f 100644 --- a/specification/comments.yml +++ b/specification/comments.yml @@ -101,3 +101,9 @@ tests: data: { } template: '12345 {{! Comment Block! }} 67890' expected: '12345 67890' + + - name: Variable Name Collision + desc: Comments must never render, even if variable with same name exists. + data: { '! comment': 1, '! comment ': 2, '!comment': 3, 'comment': 4} + template: 'comments never show: >{{! comment }}<' + expected: 'comments never show: ><' diff --git a/specification/interpolation.yml b/specification/interpolation.yml index 1b6fff1..1088b21 100644 --- a/specification/interpolation.yml +++ b/specification/interpolation.yml @@ -41,6 +41,12 @@ tests: expected: | Hello, world! + - name: No Re-interpolation + desc: Interpolated tag output should not be re-interpolated. + data: { template: '{{planet}}', planet: 'Earth' } + template: '{{template}}: {{planet}}' + expected: '{{planet}}: Earth' + - name: HTML Escaping desc: Basic interpolation should be HTML escaped. data: { forbidden: '& " < >' } @@ -101,6 +107,24 @@ tests: template: '"{{&power}} jiggawatts!"' expected: '"1.21 jiggawatts!"' + - name: Basic Null Interpolation + desc: Nulls should interpolate as the empty string. + data: { cannot: null } + template: "I ({{cannot}}) be seen!" + expected: "I () be seen!" + + - name: Triple Mustache Null Interpolation + desc: Nulls should interpolate as the empty string. + data: { cannot: null } + template: "I ({{{cannot}}}) be seen!" + expected: "I () be seen!" + + - name: Ampersand Null Interpolation + desc: Nulls should interpolate as the empty string. + data: { cannot: null } + template: "I ({{&cannot}}) be seen!" + expected: "I () be seen!" + # Context Misses - name: Basic Context Miss Interpolation @@ -179,6 +203,61 @@ tests: template: '{{#a}}{{b.c}}{{/a}}' expected: '' + - name: Dotted Names are never single keys + desc: Dotted names shall not be parsed as single, atomic keys + data: + a.b: c + template: '{{a.b}}' + expected: '' + + - name: Dotted Names - No Masking + desc: Dotted Names in a given context are unvavailable due to dot splitting + data: + a.b: c + a: { b: d } + template: '{{a.b}}' + expected: 'd' + + # Implicit Iterators + + - name: Implicit Iterators - Basic Interpolation + desc: Unadorned tags should interpolate content into the template. + data: "world" + template: | + Hello, {{.}}! + expected: | + Hello, world! + + - name: Implicit Iterators - HTML Escaping + desc: Basic interpolation should be HTML escaped. + data: '& " < >' + template: | + These characters should be HTML escaped: {{.}} + expected: | + These characters should be HTML escaped: & " < > + + - name: Implicit Iterators - Triple Mustache + desc: Triple mustaches should interpolate without HTML escaping. + data: '& " < >' + template: | + These characters should not be HTML escaped: {{{.}}} + expected: | + These characters should not be HTML escaped: & " < > + + - name: Implicit Iterators - Ampersand + desc: Ampersand should interpolate without HTML escaping. + data: '& " < >' + template: | + These characters should not be HTML escaped: {{&.}} + expected: | + These characters should not be HTML escaped: & " < > + + - name: Implicit Iterators - Basic Integer Interpolation + desc: Integers should interpolate seamlessly. + data: 85 + template: '"{{.}} miles an hour!"' + expected: '"85 miles an hour!"' + # Whitespace Sensitivity - name: Interpolation - Surrounding Whitespace diff --git a/specification/inverted.yml b/specification/inverted.yml index 5f8e2b2..148b3f5 100644 --- a/specification/inverted.yml +++ b/specification/inverted.yml @@ -46,6 +46,12 @@ tests: template: '"{{^boolean}}This should not be rendered.{{/boolean}}"' expected: '""' + - name: Null is falsey + desc: Null is falsey. + data: { "null": null } + template: '"{{^null}}This should be rendered.{{/null}}"' + expected: '"This should be rendered."' + - name: Context desc: Objects and hashes should behave like truthy values. data: { context: { name: 'Joe' } } diff --git a/specification/partials.yml b/specification/partials.yml index 8c41543..11b33ee 100644 --- a/specification/partials.yml +++ b/specification/partials.yml @@ -42,6 +42,13 @@ tests: partials: { node: '{{content}}<{{#nodes}}{{>node}}{{/nodes}}>' } expected: 'X>' + - name: Nested + desc: The greater-than operator should work from within partials. + data: { a: "hello", b: "world" } + template: '{{>outer}}' + partials: { outer: '*{{a}} {{>inner}}*', inner: '{{b}}!' } + expected: '*hello world!*' + # Whitespace Sensitivity - name: Surrounding Whitespace diff --git a/specification/sections.yml b/specification/sections.yml index 30815c9..061732b 100644 --- a/specification/sections.yml +++ b/specification/sections.yml @@ -1,29 +1,32 @@ overview: | Section tags and End Section tags are used in combination to wrap a section - of the template for iteration + of the template for iteration. These tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Section tag MUST be followed by an End Section tag with the same content within the same section. - This tag's content names the data to replace the tag. Name resolution is as + This tag's content names the data to replace the tag. Name resolution is as follows: - 1) Split the name on periods; the first part is the name to resolve, any + 1) If the name is a single period (.), the data is the item currently + sitting atop the context stack. Skip the rest of these steps. + 2) Split the name on periods; the first part is the name to resolve, any remaining parts should be retained. - 2) Walk the context stack from top to bottom, finding the first context + 3) Walk the context stack from top to bottom, finding the first context that is a) a hash containing the name as a key OR b) an object responding to a method with the given name. - 3) If the context is a hash, the data is the value associated with the + 4) If the context is a hash, the data is the value associated with the name. - 4) If the context is an object and the method with the given name has an + 5) If the context is an object and the method with the given name has an arity of 1, the method SHOULD be called with a String containing the unprocessed contents of the sections; the data is the value returned. - 5) Otherwise, the data is the value returned by calling the method with + 6) Otherwise, the data is the value returned by calling the method with the given name. - 6) If any name parts were retained in step 1, each should be resolved + 7) If any name parts were retained in step 1, each should be resolved against a context stack containing only the result from the former resolution. If any part fails resolution, the result should be considered falsey, and should interpolate as the empty string. + If the data is not of a list type, it is coerced into a list as follows: if the data is truthy (e.g. `!!data == true`), use a single-element list containing the data, otherwise use an empty list. @@ -47,20 +50,54 @@ tests: template: '"{{#boolean}}This should not be rendered.{{/boolean}}"' expected: '""' + - name: Null is falsey + desc: Null is falsey. + data: { "null": null } + template: '"{{#null}}This should not be rendered.{{/null}}"' + expected: '""' + - name: Context desc: Objects and hashes should be pushed onto the context stack. data: { context: { name: 'Joe' } } template: '"{{#context}}Hi {{name}}.{{/context}}"' expected: '"Hi Joe."' + - name: Parent contexts + desc: Names missing in the current context are looked up in the stack. + data: { a: "foo", b: "wrong", sec: { b: "bar" }, c : { d : "baz" } } + template: '"{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}"' + expected: '"foo, bar, baz"' + + - name: Variable test + desc: | + Non-false sections have their value at the top of context, + accessible as {{.}} or through the parent context. This gives + a simple way to display content conditionally if a variable exists. + data: { foo: "bar" } + template: '"{{#foo}}{{.}} is {{foo}}{{/foo}}"' + expected: '"bar is bar"' + + - name: List Contexts + desc: All elements on the context stack should be accessible within lists. + data: + tops: + - tname: + upper: "A" + lower: "a" + middles: + - mname: "1" + bottoms: + - bname: "x" + - bname: "y" + template: '{{#tops}}{{#middles}}{{tname.lower}}{{mname}}.{{#bottoms}}{{tname.upper}}{{mname}}{{bname}}.{{/bottoms}}{{/middles}}{{/tops}}' + expected: 'a1.A1x.A1y.' + - name: Deeply Nested Contexts desc: All elements on the context stack should be accessible. data: a: { one: 1 } b: { two: 2 } - c: { three: 3 } - d: { four: 4 } - e: { five: 5 } + c: { three: 3, d : { four : 4, five : 5 } } template: | {{#a}} {{one}} @@ -70,9 +107,11 @@ tests: {{one}}{{two}}{{three}}{{two}}{{one}} {{#d}} {{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}} - {{#e}} + {{#five}} + {{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}} + {{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{four}}{{three}}{{two}}{{one}} {{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}} - {{/e}} + {{/five}} {{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}} {{/d}} {{one}}{{two}}{{three}}{{two}}{{one}} @@ -87,6 +126,8 @@ tests: 12321 1234321 123454321 + 12345654321 + 123454321 1234321 12321 121 @@ -168,6 +209,33 @@ tests: template: '"{{#list}}({{#.}}{{.}}{{/.}}){{/list}}"' expected: '"(123)(abc)"' + - name: Implicit Iterator - HTML Escaping + desc: Implicit iterators with basic interpolation should be HTML escaped. + data: + list: [ '&', '"', '<', '>' ] + template: '"{{#list}}({{.}}){{/list}}"' + expected: '"(&)(")(<)(>)"' + + - name: Implicit Iterator - Triple mustache + desc: Implicit iterators in triple mustache should interpolate without HTML escaping. + data: + list: [ '&', '"', '<', '>' ] + template: '"{{#list}}({{{.}}}){{/list}}"' + expected: '"(&)(")(<)(>)"' + + - name: Implicit Iterator - Ampersand + desc: Implicit iterators in an Ampersand tag should interpolate without HTML escaping. + data: + list: [ '&', '"', '<', '>' ] + template: '"{{#list}}({{&.}}){{/list}}"' + expected: '"(&)(")(<)(>)"' + + - name: Implicit Iterator - Root-level + desc: Implicit iterators should work on root-level lists. + data: [ { value: 'a' }, { value: 'b' } ] + template: '"{{#.}}({{value}}){{/.}}"' + expected: '"(a)(b)"' + # Dotted Names - name: Dotted Names - Truthy