Mustache Specs
Autogenerate a document containing the results of nim-mustache running mustache specs v1.2.1.
import
mustache
import
json
import
strformat
import
tables
const
specfiles = ["interpolation.json", "sections.json", "inverted.json",
"comments.json", "delimiters.json", "partials.json", "~inheritance.json"]
var
specs = newJObject()
tmpl: string
tests: JsonNode
data: JsonNode
context: Context
partials: Table[string, string]
for specfile in specfiles:
echo "📃 " & specfile
specs[specfile] = parseFile("mustache-specs/" & specfile)
tests = specs[specfile]["tests"]
assert tests.kind == JArray
for test in tests.mitems:
assert test.kind == JObject
tmpl = test["template"].getStr
data = test["data"]
partials = initTable[string, string]()
if "partials" in test:
for key, val in test["partials"]:
partials[key] = val.getStr
context = newContext(searchDirs = @[], partials = partials,
values = data.toValues)
test["output"] = %tmpl.render(context)
if test["output"].getStr == test["expected"].getStr:
test["ok"] = %"✅"
echo " ✅ ", test["name"].getStr
else:
test["ok"] = %"❌"
echo " ❌ ", test["name"].getStr
proc specsToMarkdown(specs: JsonNode): string =
var partials: string
for specfile, spec in specs.pairs:
result.add "## " & specfile & "\n\n"
result.add spec["overview"].getStr & "\n"
for test in spec["tests"]:
partials = ""
if "partials" in test:
assert test["partials"].kind == JObject
partials = "Partials:\n\n```\n"
for key, val in test["partials"]:
partials.add &"{key}: {$val}\n"
partials.add "```\n"
result.add fmt"""### {test["ok"].getStr} {test["name"].getStr}
{test["desc"].getStr}
Template:
```
{test["template"].getStr}
```
Data:
```
{$(test["data"])}
```
{partials}Expected:
```
{test["expected"].getStr}
```
Output:
```
{test["output"].getStr}
```
"""
📃 interpolation.json ✅ No Interpolation ✅ Basic Interpolation ✅ HTML Escaping ✅ Triple Mustache ✅ Ampersand ✅ Basic Integer Interpolation ✅ Triple Mustache Integer Interpolation ✅ Ampersand Integer Interpolation ✅ Basic Decimal Interpolation ✅ Triple Mustache Decimal Interpolation ✅ Ampersand Decimal Interpolation ✅ Basic Null Interpolation ✅ Triple Mustache Null Interpolation ✅ Ampersand Null Interpolation ✅ Basic Context Miss Interpolation ✅ Triple Mustache Context Miss Interpolation ✅ Ampersand Context Miss Interpolation ✅ Dotted Names - Basic Interpolation ✅ Dotted Names - Triple Mustache Interpolation ✅ Dotted Names - Ampersand Interpolation ✅ Dotted Names - Arbitrary Depth ✅ Dotted Names - Broken Chains ✅ Dotted Names - Broken Chain Resolution ✅ Dotted Names - Initial Resolution ✅ Dotted Names - Context Precedence ✅ Implicit Iterators - Basic Interpolation ✅ Implicit Iterators - HTML Escaping ✅ Implicit Iterators - Triple Mustache ✅ Implicit Iterators - Ampersand ✅ Implicit Iterators - Basic Integer Interpolation ✅ Interpolation - Surrounding Whitespace ✅ Triple Mustache - Surrounding Whitespace ✅ Ampersand - Surrounding Whitespace ✅ Interpolation - Standalone ✅ Triple Mustache - Standalone ✅ Ampersand - Standalone ✅ Interpolation With Padding ✅ Triple Mustache With Padding ✅ Ampersand With Padding 📃 sections.json ✅ Truthy ✅ Falsey ✅ Null is falsey ✅ Context ✅ Parent contexts ✅ Variable test ✅ List Contexts ✅ Deeply Nested Contexts ✅ List ✅ Empty List ✅ Doubled ✅ Nested (Truthy) ✅ Nested (Falsey) ✅ Context Misses ✅ Implicit Iterator - String ✅ Implicit Iterator - Integer ✅ Implicit Iterator - Decimal ✅ Implicit Iterator - Array ✅ Dotted Names - Truthy ✅ Dotted Names - Falsey ✅ Dotted Names - Broken Chains ✅ Surrounding Whitespace ✅ Internal Whitespace ✅ Indented Inline Sections ✅ Standalone Lines ✅ Indented Standalone Lines ✅ Standalone Line Endings ✅ Standalone Without Previous Line ✅ Standalone Without Newline ✅ Padding 📃 inverted.json ✅ Falsey ✅ Truthy ✅ Null is falsey ✅ Context ✅ List ✅ Empty List ✅ Doubled ✅ Nested (Falsey) ✅ Nested (Truthy) ✅ Context Misses ✅ Dotted Names - Truthy ✅ Dotted Names - Falsey ✅ Dotted Names - Broken Chains ✅ Surrounding Whitespace ✅ Internal Whitespace ✅ Indented Inline Sections ✅ Standalone Lines ✅ Standalone Indented Lines ✅ Standalone Line Endings ✅ Standalone Without Previous Line ✅ Standalone Without Newline ✅ Padding 📃 comments.json ✅ Inline ✅ Multiline ✅ Standalone ✅ Indented Standalone ✅ Standalone Line Endings ✅ Standalone Without Previous Line ✅ Standalone Without Newline ✅ Multiline Standalone ✅ Indented Multiline Standalone ✅ Indented Inline ✅ Surrounding Whitespace 📃 delimiters.json ✅ Pair Behavior ✅ Special Characters ✅ Sections ✅ Inverted Sections ✅ Partial Inheritence ✅ Post-Partial Behavior ✅ Surrounding Whitespace ✅ Outlying Whitespace (Inline) ✅ Standalone Tag ✅ Indented Standalone Tag ✅ Standalone Line Endings ✅ Standalone Without Previous Line ✅ Standalone Without Newline ✅ Pair with Padding 📃 partials.json ✅ Basic Behavior ✅ Failed Lookup ✅ Context ✅ Recursion ✅ Surrounding Whitespace ✅ Inline Indentation ✅ Standalone Line Endings ✅ Standalone Without Previous Line ✅ Standalone Without Newline ✅ Standalone Indentation ✅ Padding Whitespace 📃 ~inheritance.json ✅ Default ✅ Variable ✅ Triple Mustache ✅ Sections ✅ Negative Sections ✅ Mustache Injection ✅ Inherit ✅ Overridden content ✅ Data does not override block ✅ Data does not override block default ✅ Overridden parent ✅ Two overridden parents ✅ Override parent with newlines ✅ Inherit indentation ✅ Only one override ✅ Parent template ✅ Recursion ✅ Multi-level inheritance ✅ Multi-level inheritance, no sub child ✅ Text inside parent ✅ Text inside parent
interpolation.json
Interpolation tags are used to integrate dynamic content into the template.
The tag's content MUST be a non-whitespace character sequence NOT containing the current closing delimiter.
This tag's content names the data to replace the tag. A single period (.
)
indicates that the item currently sitting atop the context stack should be
used; otherwise, name resolution is as follows:
- Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
- 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.
- If the context is a hash, the data is the value associated with the name.
- If the context is an object, the data is the value returned by the method with the given name.
- 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. Data should be coerced into a string (and escaped, if appropriate) before interpolation.
The Interpolation tags MUST NOT be treated as standalone.
✅ No Interpolation
Mustache-free templates should render as-is.
Template:
Hello from {Mustache}!
Data:
{}
Expected:
Hello from {Mustache}!
Output:
Hello from {Mustache}!
✅ Basic Interpolation
Unadorned tags should interpolate content into the template.
Template:
Hello, {{subject}}!
Data:
{"subject":"world"}
Expected:
Hello, world!
Output:
Hello, world!
✅ HTML Escaping
Basic interpolation should be HTML escaped.
Template:
These characters should be HTML escaped: {{forbidden}}
Data:
{"forbidden":"& \" < >"}
Expected:
These characters should be HTML escaped: & " < >
Output:
These characters should be HTML escaped: & " < >
✅ Triple Mustache
Triple mustaches should interpolate without HTML escaping.
Template:
These characters should not be HTML escaped: {{{forbidden}}}
Data:
{"forbidden":"& \" < >"}
Expected:
These characters should not be HTML escaped: & " < >
Output:
These characters should not be HTML escaped: & " < >
✅ Ampersand
Ampersand should interpolate without HTML escaping.
Template:
These characters should not be HTML escaped: {{&forbidden}}
Data:
{"forbidden":"& \" < >"}
Expected:
These characters should not be HTML escaped: & " < >
Output:
These characters should not be HTML escaped: & " < >
✅ Basic Integer Interpolation
Integers should interpolate seamlessly.
Template:
"{{mph}} miles an hour!"
Data:
{"mph":85}
Expected:
"85 miles an hour!"
Output:
"85 miles an hour!"
✅ Triple Mustache Integer Interpolation
Integers should interpolate seamlessly.
Template:
"{{{mph}}} miles an hour!"
Data:
{"mph":85}
Expected:
"85 miles an hour!"
Output:
"85 miles an hour!"
✅ Ampersand Integer Interpolation
Integers should interpolate seamlessly.
Template:
"{{&mph}} miles an hour!"
Data:
{"mph":85}
Expected:
"85 miles an hour!"
Output:
"85 miles an hour!"
✅ Basic Decimal Interpolation
Decimals should interpolate seamlessly with proper significance.
Template:
"{{power}} jiggawatts!"
Data:
{"power":1.21}
Expected:
"1.21 jiggawatts!"
Output:
"1.21 jiggawatts!"
✅ Triple Mustache Decimal Interpolation
Decimals should interpolate seamlessly with proper significance.
Template:
"{{{power}}} jiggawatts!"
Data:
{"power":1.21}
Expected:
"1.21 jiggawatts!"
Output:
"1.21 jiggawatts!"
✅ Ampersand Decimal Interpolation
Decimals should interpolate seamlessly with proper significance.
Template:
"{{&power}} jiggawatts!"
Data:
{"power":1.21}
Expected:
"1.21 jiggawatts!"
Output:
"1.21 jiggawatts!"
✅ Basic Null Interpolation
Nulls should interpolate as the empty string.
Template:
I ({{cannot}}) be seen!
Data:
{"cannot":null}
Expected:
I () be seen!
Output:
I () be seen!
✅ Triple Mustache Null Interpolation
Nulls should interpolate as the empty string.
Template:
I ({{{cannot}}}) be seen!
Data:
{"cannot":null}
Expected:
I () be seen!
Output:
I () be seen!
✅ Ampersand Null Interpolation
Nulls should interpolate as the empty string.
Template:
I ({{&cannot}}) be seen!
Data:
{"cannot":null}
Expected:
I () be seen!
Output:
I () be seen!
✅ Basic Context Miss Interpolation
Failed context lookups should default to empty strings.
Template:
I ({{cannot}}) be seen!
Data:
{}
Expected:
I () be seen!
Output:
I () be seen!
✅ Triple Mustache Context Miss Interpolation
Failed context lookups should default to empty strings.
Template:
I ({{{cannot}}}) be seen!
Data:
{}
Expected:
I () be seen!
Output:
I () be seen!
✅ Ampersand Context Miss Interpolation
Failed context lookups should default to empty strings.
Template:
I ({{&cannot}}) be seen!
Data:
{}
Expected:
I () be seen!
Output:
I () be seen!
✅ Dotted Names - Basic Interpolation
Dotted names should be considered a form of shorthand for sections.
Template:
"{{person.name}}" == "{{#person}}{{name}}{{/person}}"
Data:
{"person":{"name":"Joe"}}
Expected:
"Joe" == "Joe"
Output:
"Joe" == "Joe"
✅ Dotted Names - Triple Mustache Interpolation
Dotted names should be considered a form of shorthand for sections.
Template:
"{{{person.name}}}" == "{{#person}}{{{name}}}{{/person}}"
Data:
{"person":{"name":"Joe"}}
Expected:
"Joe" == "Joe"
Output:
"Joe" == "Joe"
✅ Dotted Names - Ampersand Interpolation
Dotted names should be considered a form of shorthand for sections.
Template:
"{{&person.name}}" == "{{#person}}{{&name}}{{/person}}"
Data:
{"person":{"name":"Joe"}}
Expected:
"Joe" == "Joe"
Output:
"Joe" == "Joe"
✅ Dotted Names - Arbitrary Depth
Dotted names should be functional to any level of nesting.
Template:
"{{a.b.c.d.e.name}}" == "Phil"
Data:
{"a":{"b":{"c":{"d":{"e":{"name":"Phil"}}}}}}
Expected:
"Phil" == "Phil"
Output:
"Phil" == "Phil"
✅ Dotted Names - Broken Chains
Any falsey value prior to the last part of the name should yield ''.
Template:
"{{a.b.c}}" == ""
Data:
{"a":{}}
Expected:
"" == ""
Output:
"" == ""
✅ Dotted Names - Broken Chain Resolution
Each part of a dotted name should resolve only against its parent.
Template:
"{{a.b.c.name}}" == ""
Data:
{"a":{"b":{}},"c":{"name":"Jim"}}
Expected:
"" == ""
Output:
"" == ""
✅ Dotted Names - Initial Resolution
The first part of a dotted name should resolve as any other name.
Template:
"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"
Data:
{"a":{"b":{"c":{"d":{"e":{"name":"Phil"}}}}},"b":{"c":{"d":{"e":{"name":"Wrong"}}}}}
Expected:
"Phil" == "Phil"
Output:
"Phil" == "Phil"
✅ Dotted Names - Context Precedence
Dotted names should be resolved against former resolutions.
Template:
{{#a}}{{b.c}}{{/a}}
Data:
{"a":{"b":{}},"b":{"c":"ERROR"}}
Expected:
Output:
✅ Implicit Iterators - Basic Interpolation
Unadorned tags should interpolate content into the template.
Template:
Hello, {{.}}!
Data:
"world"
Expected:
Hello, world!
Output:
Hello, world!
✅ Implicit Iterators - HTML Escaping
Basic interpolation should be HTML escaped.
Template:
These characters should be HTML escaped: {{.}}
Data:
"& \" < >"
Expected:
These characters should be HTML escaped: & " < >
Output:
These characters should be HTML escaped: & " < >
✅ Implicit Iterators - Triple Mustache
Triple mustaches should interpolate without HTML escaping.
Template:
These characters should not be HTML escaped: {{{.}}}
Data:
"& \" < >"
Expected:
These characters should not be HTML escaped: & " < >
Output:
These characters should not be HTML escaped: & " < >
✅ Implicit Iterators - Ampersand
Ampersand should interpolate without HTML escaping.
Template:
These characters should not be HTML escaped: {{&.}}
Data:
"& \" < >"
Expected:
These characters should not be HTML escaped: & " < >
Output:
These characters should not be HTML escaped: & " < >
✅ Implicit Iterators - Basic Integer Interpolation
Integers should interpolate seamlessly.
Template:
"{{.}} miles an hour!"
Data:
85
Expected:
"85 miles an hour!"
Output:
"85 miles an hour!"
✅ Interpolation - Surrounding Whitespace
Interpolation should not alter surrounding whitespace.
Template:
| {{string}} |
Data:
{"string":"---"}
Expected:
| --- |
Output:
| --- |
✅ Triple Mustache - Surrounding Whitespace
Interpolation should not alter surrounding whitespace.
Template:
| {{{string}}} |
Data:
{"string":"---"}
Expected:
| --- |
Output:
| --- |
✅ Ampersand - Surrounding Whitespace
Interpolation should not alter surrounding whitespace.
Template:
| {{&string}} |
Data:
{"string":"---"}
Expected:
| --- |
Output:
| --- |
✅ Interpolation - Standalone
Standalone interpolation should not alter surrounding whitespace.
Template:
{{string}}
Data:
{"string":"---"}
Expected:
---
Output:
---
✅ Triple Mustache - Standalone
Standalone interpolation should not alter surrounding whitespace.
Template:
{{{string}}}
Data:
{"string":"---"}
Expected:
---
Output:
---
✅ Ampersand - Standalone
Standalone interpolation should not alter surrounding whitespace.
Template:
{{&string}}
Data:
{"string":"---"}
Expected:
---
Output:
---
✅ Interpolation With Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{ string }}|
Data:
{"string":"---"}
Expected:
|---|
Output:
|---|
✅ Triple Mustache With Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{{ string }}}|
Data:
{"string":"---"}
Expected:
|---|
Output:
|---|
✅ Ampersand With Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{& string }}|
Data:
{"string":"---"}
Expected:
|---|
Output:
|---|
sections.json
Section tags and End Section tags are used in combination to wrap a section 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 follows:
- Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
- 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.
- If the context is a hash, the data is the value associated with the name.
- 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.
- Otherwise, the data is the value returned by calling the method with the given name.
- 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.
For each element in the data list, the element MUST be pushed onto the context stack, the section MUST be rendered, and the element MUST be popped off the context stack.
Section and End Section tags SHOULD be treated as standalone when appropriate.
✅ Truthy
Truthy sections should have their contents rendered.
Template:
"{{#boolean}}This should be rendered.{{/boolean}}"
Data:
{"boolean":true}
Expected:
"This should be rendered."
Output:
"This should be rendered."
✅ Falsey
Falsey sections should have their contents omitted.
Template:
"{{#boolean}}This should not be rendered.{{/boolean}}"
Data:
{"boolean":false}
Expected:
""
Output:
""
✅ Null is falsey
Null is falsey.
Template:
"{{#null}}This should not be rendered.{{/null}}"
Data:
{"null":null}
Expected:
""
Output:
""
✅ Context
Objects and hashes should be pushed onto the context stack.
Template:
"{{#context}}Hi {{name}}.{{/context}}"
Data:
{"context":{"name":"Joe"}}
Expected:
"Hi Joe."
Output:
"Hi Joe."
✅ Parent contexts
Names missing in the current context are looked up in the stack.
Template:
"{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}"
Data:
{"a":"foo","b":"wrong","sec":{"b":"bar"},"c":{"d":"baz"}}
Expected:
"foo, bar, baz"
Output:
"foo, bar, baz"
✅ Variable test
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.
Template:
"{{#foo}}{{.}} is {{foo}}{{/foo}}"
Data:
{"foo":"bar"}
Expected:
"bar is bar"
Output:
"bar is bar"
✅ List Contexts
All elements on the context stack should be accessible within lists.
Template:
{{#tops}}{{#middles}}{{tname.lower}}{{mname}}.{{#bottoms}}{{tname.upper}}{{mname}}{{bname}}.{{/bottoms}}{{/middles}}{{/tops}}
Data:
{"tops":[{"tname":{"upper":"A","lower":"a"},"middles":[{"mname":"1","bottoms":[{"bname":"x"},{"bname":"y"}]}]}]}
Expected:
a1.A1x.A1y.
Output:
a1.A1x.A1y.
✅ Deeply Nested Contexts
All elements on the context stack should be accessible.
Template:
{{#a}}
{{one}}
{{#b}}
{{one}}{{two}}{{one}}
{{#c}}
{{one}}{{two}}{{three}}{{two}}{{one}}
{{#d}}
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
{{#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}}
{{/five}}
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
{{/d}}
{{one}}{{two}}{{three}}{{two}}{{one}}
{{/c}}
{{one}}{{two}}{{one}}
{{/b}}
{{one}}
{{/a}}
Data:
{"a":{"one":1},"b":{"two":2},"c":{"three":3,"d":{"four":4,"five":5}}}
Expected:
1
121
12321
1234321
123454321
12345654321
123454321
1234321
12321
121
1
Output:
1
121
12321
1234321
123454321
12345654321
123454321
1234321
12321
121
1
✅ List
Lists should be iterated; list items should visit the context stack.
Template:
"{{#list}}{{item}}{{/list}}"
Data:
{"list":[{"item":1},{"item":2},{"item":3}]}
Expected:
"123"
Output:
"123"
✅ Empty List
Empty lists should behave like falsey values.
Template:
"{{#list}}Yay lists!{{/list}}"
Data:
{"list":[]}
Expected:
""
Output:
""
✅ Doubled
Multiple sections per template should be permitted.
Template:
{{#bool}}
* first
{{/bool}}
* {{two}}
{{#bool}}
* third
{{/bool}}
Data:
{"bool":true,"two":"second"}
Expected:
* first
* second
* third
Output:
* first
* second
* third
✅ Nested (Truthy)
Nested truthy sections should have their contents rendered.
Template:
| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |
Data:
{"bool":true}
Expected:
| A B C D E |
Output:
| A B C D E |
✅ Nested (Falsey)
Nested falsey sections should be omitted.
Template:
| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |
Data:
{"bool":false}
Expected:
| A E |
Output:
| A E |
✅ Context Misses
Failed context lookups should be considered falsey.
Template:
[{{#missing}}Found key 'missing'!{{/missing}}]
Data:
{}
Expected:
[]
Output:
[]
✅ Implicit Iterator - String
Implicit iterators should directly interpolate strings.
Template:
"{{#list}}({{.}}){{/list}}"
Data:
{"list":["a","b","c","d","e"]}
Expected:
"(a)(b)(c)(d)(e)"
Output:
"(a)(b)(c)(d)(e)"
✅ Implicit Iterator - Integer
Implicit iterators should cast integers to strings and interpolate.
Template:
"{{#list}}({{.}}){{/list}}"
Data:
{"list":[1,2,3,4,5]}
Expected:
"(1)(2)(3)(4)(5)"
Output:
"(1)(2)(3)(4)(5)"
✅ Implicit Iterator - Decimal
Implicit iterators should cast decimals to strings and interpolate.
Template:
"{{#list}}({{.}}){{/list}}"
Data:
{"list":[1.1,2.2,3.3,4.4,5.5]}
Expected:
"(1.1)(2.2)(3.3)(4.4)(5.5)"
Output:
"(1.1)(2.2)(3.3)(4.4)(5.5)"
✅ Implicit Iterator - Array
Implicit iterators should allow iterating over nested arrays.
Template:
"{{#list}}({{#.}}{{.}}{{/.}}){{/list}}"
Data:
{"list":[[1,2,3],["a","b","c"]]}
Expected:
"(123)(abc)"
Output:
"(123)(abc)"
✅ Dotted Names - Truthy
Dotted names should be valid for Section tags.
Template:
"{{#a.b.c}}Here{{/a.b.c}}" == "Here"
Data:
{"a":{"b":{"c":true}}}
Expected:
"Here" == "Here"
Output:
"Here" == "Here"
✅ Dotted Names - Falsey
Dotted names should be valid for Section tags.
Template:
"{{#a.b.c}}Here{{/a.b.c}}" == ""
Data:
{"a":{"b":{"c":false}}}
Expected:
"" == ""
Output:
"" == ""
✅ Dotted Names - Broken Chains
Dotted names that cannot be resolved should be considered falsey.
Template:
"{{#a.b.c}}Here{{/a.b.c}}" == ""
Data:
{"a":{}}
Expected:
"" == ""
Output:
"" == ""
✅ Surrounding Whitespace
Sections should not alter surrounding whitespace.
Template:
| {{#boolean}} | {{/boolean}} |
Data:
{"boolean":true}
Expected:
| | |
Output:
| | |
✅ Internal Whitespace
Sections should not alter internal whitespace.
Template:
| {{#boolean}} {{! Important Whitespace }}
{{/boolean}} |
Data:
{"boolean":true}
Expected:
|
|
Output:
|
|
✅ Indented Inline Sections
Single-line sections should not alter surrounding whitespace.
Template:
{{#boolean}}YES{{/boolean}}
{{#boolean}}GOOD{{/boolean}}
Data:
{"boolean":true}
Expected:
YES
GOOD
Output:
YES
GOOD
✅ Standalone Lines
Standalone lines should be removed from the template.
Template:
| This Is
{{#boolean}}
|
{{/boolean}}
| A Line
Data:
{"boolean":true}
Expected:
| This Is
|
| A Line
Output:
| This Is
|
| A Line
✅ Indented Standalone Lines
Indented standalone lines should be removed from the template.
Template:
| This Is
{{#boolean}}
|
{{/boolean}}
| A Line
Data:
{"boolean":true}
Expected:
| This Is
|
| A Line
Output:
| This Is
|
| A Line
✅ Standalone Line Endings
"\r\n" should be considered a newline for standalone tags.
Template:
|
{{#boolean}}
{{/boolean}}
|
Data:
{"boolean":true}
Expected:
|
|
Output:
|
|
✅ Standalone Without Previous Line
Standalone tags should not require a newline to precede them.
Template:
{{#boolean}}
#{{/boolean}}
/
Data:
{"boolean":true}
Expected:
#
/
Output:
#
/
✅ Standalone Without Newline
Standalone tags should not require a newline to follow them.
Template:
#{{#boolean}}
/
{{/boolean}}
Data:
{"boolean":true}
Expected:
#
/
Output:
#
/
✅ Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{# boolean }}={{/ boolean }}|
Data:
{"boolean":true}
Expected:
|=|
Output:
|=|
inverted.json
Inverted Section tags and End Section tags are used in combination to wrap a section of the template.
These tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Inverted 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 follows:
- Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
- 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.
- If the context is a hash, the data is the value associated with the name.
- 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.
- Otherwise, the data is the value returned by calling the method with the given name.
- 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.
This section MUST NOT be rendered unless the data list is empty.
Inverted Section and End Section tags SHOULD be treated as standalone when appropriate.
✅ Falsey
Falsey sections should have their contents rendered.
Template:
"{{^boolean}}This should be rendered.{{/boolean}}"
Data:
{"boolean":false}
Expected:
"This should be rendered."
Output:
"This should be rendered."
✅ Truthy
Truthy sections should have their contents omitted.
Template:
"{{^boolean}}This should not be rendered.{{/boolean}}"
Data:
{"boolean":true}
Expected:
""
Output:
""
✅ Null is falsey
Null is falsey.
Template:
"{{^null}}This should be rendered.{{/null}}"
Data:
{"null":null}
Expected:
"This should be rendered."
Output:
"This should be rendered."
✅ Context
Objects and hashes should behave like truthy values.
Template:
"{{^context}}Hi {{name}}.{{/context}}"
Data:
{"context":{"name":"Joe"}}
Expected:
""
Output:
""
✅ List
Lists should behave like truthy values.
Template:
"{{^list}}{{n}}{{/list}}"
Data:
{"list":[{"n":1},{"n":2},{"n":3}]}
Expected:
""
Output:
""
✅ Empty List
Empty lists should behave like falsey values.
Template:
"{{^list}}Yay lists!{{/list}}"
Data:
{"list":[]}
Expected:
"Yay lists!"
Output:
"Yay lists!"
✅ Doubled
Multiple inverted sections per template should be permitted.
Template:
{{^bool}}
* first
{{/bool}}
* {{two}}
{{^bool}}
* third
{{/bool}}
Data:
{"bool":false,"two":"second"}
Expected:
* first
* second
* third
Output:
* first
* second
* third
✅ Nested (Falsey)
Nested falsey sections should have their contents rendered.
Template:
| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |
Data:
{"bool":false}
Expected:
| A B C D E |
Output:
| A B C D E |
✅ Nested (Truthy)
Nested truthy sections should be omitted.
Template:
| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |
Data:
{"bool":true}
Expected:
| A E |
Output:
| A E |
✅ Context Misses
Failed context lookups should be considered falsey.
Template:
[{{^missing}}Cannot find key 'missing'!{{/missing}}]
Data:
{}
Expected:
[Cannot find key 'missing'!]
Output:
[Cannot find key 'missing'!]
✅ Dotted Names - Truthy
Dotted names should be valid for Inverted Section tags.
Template:
"{{^a.b.c}}Not Here{{/a.b.c}}" == ""
Data:
{"a":{"b":{"c":true}}}
Expected:
"" == ""
Output:
"" == ""
✅ Dotted Names - Falsey
Dotted names should be valid for Inverted Section tags.
Template:
"{{^a.b.c}}Not Here{{/a.b.c}}" == "Not Here"
Data:
{"a":{"b":{"c":false}}}
Expected:
"Not Here" == "Not Here"
Output:
"Not Here" == "Not Here"
✅ Dotted Names - Broken Chains
Dotted names that cannot be resolved should be considered falsey.
Template:
"{{^a.b.c}}Not Here{{/a.b.c}}" == "Not Here"
Data:
{"a":{}}
Expected:
"Not Here" == "Not Here"
Output:
"Not Here" == "Not Here"
✅ Surrounding Whitespace
Inverted sections should not alter surrounding whitespace.
Template:
| {{^boolean}} | {{/boolean}} |
Data:
{"boolean":false}
Expected:
| | |
Output:
| | |
✅ Internal Whitespace
Inverted should not alter internal whitespace.
Template:
| {{^boolean}} {{! Important Whitespace }}
{{/boolean}} |
Data:
{"boolean":false}
Expected:
|
|
Output:
|
|
✅ Indented Inline Sections
Single-line sections should not alter surrounding whitespace.
Template:
{{^boolean}}NO{{/boolean}}
{{^boolean}}WAY{{/boolean}}
Data:
{"boolean":false}
Expected:
NO
WAY
Output:
NO
WAY
✅ Standalone Lines
Standalone lines should be removed from the template.
Template:
| This Is
{{^boolean}}
|
{{/boolean}}
| A Line
Data:
{"boolean":false}
Expected:
| This Is
|
| A Line
Output:
| This Is
|
| A Line
✅ Standalone Indented Lines
Standalone indented lines should be removed from the template.
Template:
| This Is
{{^boolean}}
|
{{/boolean}}
| A Line
Data:
{"boolean":false}
Expected:
| This Is
|
| A Line
Output:
| This Is
|
| A Line
✅ Standalone Line Endings
"\r\n" should be considered a newline for standalone tags.
Template:
|
{{^boolean}}
{{/boolean}}
|
Data:
{"boolean":false}
Expected:
|
|
Output:
|
|
✅ Standalone Without Previous Line
Standalone tags should not require a newline to precede them.
Template:
{{^boolean}}
^{{/boolean}}
/
Data:
{"boolean":false}
Expected:
^
/
Output:
^
/
✅ Standalone Without Newline
Standalone tags should not require a newline to follow them.
Template:
^{{^boolean}}
/
{{/boolean}}
Data:
{"boolean":false}
Expected:
^
/
Output:
^
/
✅ Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{^ boolean }}={{/ boolean }}|
Data:
{"boolean":false}
Expected:
|=|
Output:
|=|
comments.json
Comment tags represent content that should never appear in the resulting output.
The tag's content may contain any substring (including newlines) EXCEPT the closing delimiter.
Comment tags SHOULD be treated as standalone when appropriate.
✅ Inline
Comment blocks should be removed from the template.
Template:
12345{{! Comment Block! }}67890
Data:
{}
Expected:
1234567890
Output:
1234567890
✅ Multiline
Multiline comments should be permitted.
Template:
12345{{!
This is a
multi-line comment...
}}67890
Data:
{}
Expected:
1234567890
Output:
1234567890
✅ Standalone
All standalone comment lines should be removed.
Template:
Begin.
{{! Comment Block! }}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Indented Standalone
All standalone comment lines should be removed.
Template:
Begin.
{{! Indented Comment Block! }}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Standalone Line Endings
"\r\n" should be considered a newline for standalone tags.
Template:
|
{{! Standalone Comment }}
|
Data:
{}
Expected:
|
|
Output:
|
|
✅ Standalone Without Previous Line
Standalone tags should not require a newline to precede them.
Template:
{{! I'm Still Standalone }}
!
Data:
{}
Expected:
!
Output:
!
✅ Standalone Without Newline
Standalone tags should not require a newline to follow them.
Template:
!
{{! I'm Still Standalone }}
Data:
{}
Expected:
!
Output:
!
✅ Multiline Standalone
All standalone comment lines should be removed.
Template:
Begin.
{{!
Something's going on here...
}}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Indented Multiline Standalone
All standalone comment lines should be removed.
Template:
Begin.
{{!
Something's going on here...
}}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Indented Inline
Inline comments should not strip whitespace
Template:
12 {{! 34 }}
Data:
{}
Expected:
12
Output:
12
✅ Surrounding Whitespace
Comment removal should preserve surrounding whitespace.
Template:
12345 {{! Comment Block! }} 67890
Data:
{}
Expected:
12345 67890
Output:
12345 67890
delimiters.json
Set Delimiter tags are used to change the tag delimiters for all content following the tag in the current compilation unit.
The tag's content MUST be any two non-whitespace sequences (separated by whitespace) EXCEPT an equals sign ('=') followed by the current closing delimiter.
Set Delimiter tags SHOULD be treated as standalone when appropriate.
✅ Pair Behavior
The equals sign (used on both sides) should permit delimiter changes.
Template:
{{=<% %>=}}(<%text%>)
Data:
{"text":"Hey!"}
Expected:
(Hey!)
Output:
(Hey!)
✅ Special Characters
Characters with special meaning regexen should be valid delimiters.
Template:
({{=[ ]=}}[text])
Data:
{"text":"It worked!"}
Expected:
(It worked!)
Output:
(It worked!)
✅ Sections
Delimiters set outside sections should persist.
Template:
[
{{#section}}
{{data}}
|data|
{{/section}}
{{= | | =}}
|#section|
{{data}}
|data|
|/section|
]
Data:
{"section":true,"data":"I got interpolated."}
Expected:
[
I got interpolated.
|data|
{{data}}
I got interpolated.
]
Output:
[
I got interpolated.
|data|
{{data}}
I got interpolated.
]
✅ Inverted Sections
Delimiters set outside inverted sections should persist.
Template:
[
{{^section}}
{{data}}
|data|
{{/section}}
{{= | | =}}
|^section|
{{data}}
|data|
|/section|
]
Data:
{"section":false,"data":"I got interpolated."}
Expected:
[
I got interpolated.
|data|
{{data}}
I got interpolated.
]
Output:
[
I got interpolated.
|data|
{{data}}
I got interpolated.
]
✅ Partial Inheritence
Delimiters set in a parent template should not affect a partial.
Template:
[ {{>include}} ]
{{= | | =}}
[ |>include| ]
Data:
{"value":"yes"}
Partials:
include: ".{{value}}."
Expected:
[ .yes. ]
[ .yes. ]
Output:
[ .yes. ]
[ .yes. ]
✅ Post-Partial Behavior
Delimiters set in a partial should not affect the parent template.
Template:
[ {{>include}} ]
[ .{{value}}. .|value|. ]
Data:
{"value":"yes"}
Partials:
include: ".{{value}}. {{= | | =}} .|value|."
Expected:
[ .yes. .yes. ]
[ .yes. .|value|. ]
Output:
[ .yes. .yes. ]
[ .yes. .|value|. ]
✅ Surrounding Whitespace
Surrounding whitespace should be left untouched.
Template:
| {{=@ @=}} |
Data:
{}
Expected:
| |
Output:
| |
✅ Outlying Whitespace (Inline)
Whitespace should be left untouched.
Template:
| {{=@ @=}}
Data:
{}
Expected:
|
Output:
|
✅ Standalone Tag
Standalone lines should be removed from the template.
Template:
Begin.
{{=@ @=}}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Indented Standalone Tag
Indented standalone lines should be removed from the template.
Template:
Begin.
{{=@ @=}}
End.
Data:
{}
Expected:
Begin.
End.
Output:
Begin.
End.
✅ Standalone Line Endings
"\r\n" should be considered a newline for standalone tags.
Template:
|
{{= @ @ =}}
|
Data:
{}
Expected:
|
|
Output:
|
|
✅ Standalone Without Previous Line
Standalone tags should not require a newline to precede them.
Template:
{{=@ @=}}
=
Data:
{}
Expected:
=
Output:
=
✅ Standalone Without Newline
Standalone tags should not require a newline to follow them.
Template:
=
{{=@ @=}}
Data:
{}
Expected:
=
Output:
=
✅ Pair with Padding
Superfluous in-tag whitespace should be ignored.
Template:
|{{= @ @ =}}|
Data:
{}
Expected:
||
Output:
||
partials.json
Partial tags are used to expand an external template into the current template.
The tag's content MUST be a non-whitespace character sequence NOT containing the current closing delimiter.
This tag's content names the partial to inject. Set Delimiter tags MUST NOT affect the parsing of a partial. The partial MUST be rendered against the context stack local to the tag. If the named partial cannot be found, the empty string SHOULD be used instead, as in interpolations.
Partial tags SHOULD be treated as standalone when appropriate. If this tag is used standalone, any whitespace preceding the tag should treated as indentation, and prepended to each line of the partial before rendering.
✅ Basic Behavior
The greater-than operator should expand to the named partial.
Template:
"{{>text}}"
Data:
{}
Partials:
text: "from partial"
Expected:
"from partial"
Output:
"from partial"
✅ Failed Lookup
The empty string should be used when the named partial is not found.
Template:
"{{>text}}"
Data:
{}
Partials:
Expected:
""
Output:
""
✅ Context
The greater-than operator should operate within the current context.
Template:
"{{>partial}}"
Data:
{"text":"content"}
Partials:
partial: "*{{text}}*"
Expected:
"*content*"
Output:
"*content*"
✅ Recursion
The greater-than operator should properly recurse.
Template:
{{>node}}
Data:
{"content":"X","nodes":[{"content":"Y","nodes":[]}]}
Partials:
node: "{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"
Expected:
X<Y<>>
Output:
X<Y<>>
✅ Surrounding Whitespace
The greater-than operator should not alter surrounding whitespace.
Template:
| {{>partial}} |
Data:
{}
Partials:
partial: "\t|\t"
Expected:
| | |
Output:
| | |
✅ Inline Indentation
Whitespace should be left untouched.
Template:
{{data}} {{> partial}}
Data:
{"data":"|"}
Partials:
partial: ">\n>"
Expected:
| >
>
Output:
| >
>
✅ Standalone Line Endings
"\r\n" should be considered a newline for standalone tags.
Template:
|
{{>partial}}
|
Data:
{}
Partials:
partial: ">"
Expected:
|
>|
Output:
|
>|
✅ Standalone Without Previous Line
Standalone tags should not require a newline to precede them.
Template:
{{>partial}}
>
Data:
{}
Partials:
partial: ">\n>"
Expected:
>
>>
Output:
>
>>
✅ Standalone Without Newline
Standalone tags should not require a newline to follow them.
Template:
>
{{>partial}}
Data:
{}
Partials:
partial: ">\n>"
Expected:
>
>
>
Output:
>
>
>
✅ Standalone Indentation
Each line of the partial should be indented before rendering.
Template:
\
{{>partial}}
/
Data:
{"content":"<\n->"}
Partials:
partial: "|\n{{{content}}}\n|\n"
Expected:
\
|
<
->
|
/
Output:
\
|
<
->
|
/
✅ Padding Whitespace
Superfluous in-tag whitespace should be ignored.
Template:
|{{> partial }}|
Data:
{"boolean":true}
Partials:
partial: "[]"
Expected:
|[]|
Output:
|[]|
~inheritance.json
Like partials, Parent tags are used to expand an external template into the current template. Unlike partials, Parent tags may contain optional arguments delimited by Block tags. For this reason, Parent tags may also be referred to as Parametric Partials.
The Parent tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Parent tag MUST be followed by an End Section tag with the same content within the matching Parent tag.
This tag's content names the Parent template to inject. Set Delimiter tags Preceding a Parent tag MUST NOT affect the parsing of the injected external template. The Parent MUST be rendered against the context stack local to the tag. If the named Parent cannot be found, the empty string SHOULD be used instead, as in interpolations.
Parent tags SHOULD be treated as standalone when appropriate. If this tag is used standalone, any whitespace preceding the tag should be treated as indentation, and prepended to each line of the Parent before rendering.
The Block tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter. Each Block tag MUST be followed by an End Section tag with the same content within the matching Block tag. This tag's content determines the parameter or argument name.
Block tags may appear both inside and outside of Parent tags. In both cases, they specify a position within the template that can be overridden; it is a parameter of the containing template. The template text between the Block tag and its matching End Section tag defines the default content to render when the parameter is not overridden from outside.
In addition, when used inside of a Parent tag, the template text between a Block tag and its matching End Section tag defines content that replaces the default defined in the Parent template. This content is the argument passed to the Parent template.
The practice of injecting an external template using a Parent tag is referred to as inheritance. If the Parent tag includes a Block tag that overrides a parameter of the Parent template, this may also be referred to as substitution.
Parent templates are taken from the same namespace as regular Partial templates and in fact, injecting a regular Partial is exactly equivalent to injecting a Parent without making any substitutions. Parameter and arguments names live in a namespace that is distinct from both Partials and the context.
✅ Default
Default content should be rendered if the block isn't overridden
Template:
{{$title}}Default title{{/title}}
Data:
{}
Expected:
Default title
Output:
Default title
✅ Variable
Default content renders variables
Template:
{{$foo}}default {{bar}} content{{/foo}}
Data:
{"bar":"baz"}
Expected:
default baz content
Output:
default baz content
✅ Triple Mustache
Default content renders triple mustache variables
Template:
{{$foo}}default {{{bar}}} content{{/foo}}
Data:
{"bar":"<baz>"}
Expected:
default <baz> content
Output:
default <baz> content
✅ Sections
Default content renders sections
Template:
{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}
Data:
{"bar":{"baz":"qux"}}
Expected:
default qux content
Output:
default qux content
✅ Negative Sections
Default content renders negative sections
Template:
{{$foo}}default {{^bar}}{{baz}}{{/bar}} content{{/foo}}
Data:
{"baz":"three"}
Expected:
default three content
Output:
default three content
✅ Mustache Injection
Mustache injection in default content
Template:
{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}
Data:
{"bar":{"baz":"{{qux}}"}}
Expected:
default {{qux}} content
Output:
default {{qux}} content
✅ Inherit
Default content rendered inside inherited templates
Template:
{{<include}}{{/include}}
Data:
{}
Partials:
include: "{{$foo}}default content{{/foo}}"
Expected:
default content
Output:
default content
✅ Overridden content
Overridden content
Template:
{{<super}}{{$title}}sub template title{{/title}}{{/super}}
Data:
{}
Partials:
super: "...{{$title}}Default title{{/title}}..."
Expected:
...sub template title...
Output:
...sub template title...
✅ Data does not override block
Context does not override argument passed into parent
Template:
{{<include}}{{$var}}var in template{{/var}}{{/include}}
Data:
{"var":"var in data"}
Partials:
include: "{{$var}}var in include{{/var}}"
Expected:
var in template
Output:
var in template
✅ Data does not override block default
Context does not override default content of block
Template:
{{<include}}{{/include}}
Data:
{"var":"var in data"}
Partials:
include: "{{$var}}var in include{{/var}}"
Expected:
var in include
Output:
var in include
✅ Overridden parent
Overridden parent
Template:
test {{<parent}}{{$stuff}}override{{/stuff}}{{/parent}}
Data:
{}
Partials:
parent: "{{$stuff}}...{{/stuff}}"
Expected:
test override
Output:
test override
✅ Two overridden parents
Two overridden parents with different content
Template:
test {{<parent}}{{$stuff}}override1{{/stuff}}{{/parent}} {{<parent}}{{$stuff}}override2{{/stuff}}{{/parent}}
Data:
{}
Partials:
parent: "|{{$stuff}}...{{/stuff}}{{$default}} default{{/default}}|"
Expected:
test |override1 default| |override2 default|
Output:
test |override1 default| |override2 default|
✅ Override parent with newlines
Override parent with newlines
Template:
{{<parent}}{{$ballmer}}
peaked
:(
{{/ballmer}}{{/parent}}
Data:
{}
Partials:
parent: "{{$ballmer}}peaking{{/ballmer}}"
Expected:
peaked
:(
Output:
peaked
:(
✅ Inherit indentation
Inherit indentation when overriding a parent
Template:
{{<parent}}{{$nineties}}hammer time{{/nineties}}{{/parent}}
Data:
{}
Partials:
parent: "stop:\n {{$nineties}}collaborate and listen{{/nineties}}\n"
Expected:
stop:
hammer time
Output:
stop:
hammer time
✅ Only one override
Override one parameter but not the other
Template:
{{<parent}}{{$stuff2}}override two{{/stuff2}}{{/parent}}
Data:
{}
Partials:
parent: "{{$stuff}}new default one{{/stuff}}, {{$stuff2}}new default two{{/stuff2}}"
Expected:
new default one, override two
Output:
new default one, override two
✅ Parent template
Parent templates behave identically to partials when called with no parameters
Template:
{{>parent}}|{{<parent}}{{/parent}}
Data:
{}
Partials:
parent: "{{$foo}}default content{{/foo}}"
Expected:
default content|default content
Output:
default content|default content
✅ Recursion
Recursion in inherited templates
Template:
{{<parent}}{{$foo}}override{{/foo}}{{/parent}}
Data:
{}
Partials:
parent: "{{$foo}}default content{{/foo}} {{$bar}}{{<parent2}}{{/parent2}}{{/bar}}"
parent2: "{{$foo}}parent2 default content{{/foo}} {{<parent}}{{$bar}}don't recurse{{/bar}}{{/parent}}"
Expected:
override override override don't recurse
Output:
override override override don't recurse
✅ Multi-level inheritance
Top-level substitutions take precedence in multi-level inheritance
Template:
{{<parent}}{{$a}}c{{/a}}{{/parent}}
Data:
{}
Partials:
parent: "{{<older}}{{$a}}p{{/a}}{{/older}}"
older: "{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"
grandParent: "{{$a}}g{{/a}}"
Expected:
c
Output:
c
✅ Multi-level inheritance, no sub child
Top-level substitutions take precedence in multi-level inheritance
Template:
{{<parent}}{{/parent}}
Data:
{}
Partials:
parent: "{{<older}}{{$a}}p{{/a}}{{/older}}"
older: "{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"
grandParent: "{{$a}}g{{/a}}"
Expected:
p
Output:
p
✅ Text inside parent
Ignores text inside parent templates, but does parse $ tags
Template:
{{<parent}} asdfasd {{$foo}}hmm{{/foo}} asdfasdfasdf {{/parent}}
Data:
{}
Partials:
parent: "{{$foo}}default content{{/foo}}"
Expected:
hmm
Output:
hmm
✅ Text inside parent
Allows text inside a parent tag, but ignores it
Template:
{{<parent}} asdfasd asdfasdfasdf {{/parent}}
Data:
{}
Partials:
parent: "{{$foo}}default content{{/foo}}"
Expected:
default content
Output:
default content