Conformance Suite - JsRender

This is a conformance suite taken (with small adjustments) from Mike Samuel's jQuery Templates implementation proposal, for testing conformance of JsRender.

Each row in the tables below is a test case.

This is work in progress. Note that not all the cases correspond exactly to an agreed jQuery Templates grammar. The grammar here is close, but not yet definitive. The following documentation, though less detailed, is definitive for the current jQuery Templates design.

Legend

The "test" column includes the literal text of a template wrapped in an <xmp> element.

The "data" column includes a data value as JavaScript text also wrapped in an <xmp>.

The "expected" column shows the expected output of the template when compiled and applied to the data. If the output starts with "Error:" then the compilation or application should fail with an exception, and in debug mode should produce the following error message.

The "actual" column is auto-generated and contains the actual result and is colored to indicate success or failure.

The right-most column, if any, includes explatory text about the test-case.

Any "test" cells whose content consists of named templates specified via <script type="text/x-jquery-tmpl" id="template-name"> elements will have all templates extracted and compiled as a bundle and the template with id="main" will be the one applied.

Usage

To test the reference implementation, add ?reference to the URL.

To test the reference implementation, add ?compressed to the URL.

To run just some tests, add ?testFilter=<RegExp> where <RegExp> matches the right-most column, the test description.

To turn off logging to the console, add ?nolog to the URL.

The tests below test the built-in operators that ship by default.

TestDataExpectedActual
{} Empty Template
Hello, World! {} Hello, World! Plain text
Hello, &lt;World&gt;! {} Hello, <World>! HTML entities preserved
𐀀 {} 𐀀 Supplemental codepoints preserved
�-� {} �-� Orphaned surrogates preserved
foo&#10;${"\n"} bar {} foo bar Newlines preserved.
a
b${'\u2029'}c {} a
b
c Unusual newlines
<span style=color:red>Hello</span>, World! {} Hello, World! HTML tags preserved
It costs $5, 5$. {if} {{/- {{. {} It costs $5, 5$. {if} {{/- {{. Dollars and curlies in text
Cow says m${x}, dog says ${y}k { x: 'foo', y: 'bar' } Cow says mfoo, dog says bark Substitution
Cow says m{{= x}}, dog says {{= y}}k { x: 'foo', y: 'bar' } Cow says mfoo, dog says bark Substitution unabbreviated syntax
Hello, ${world}! {} Hello, ! Substitution w/out data
i=${i}, f=${f}, nf=${nf}, nan=${nan}, inf=${inf}, nzero=${nzero} { i: 42, f: 0.5, nf: -0.5, nan: NaN, inf: Infinity, nzero: -0 } i=42, f=0.5, nf=-0.5, nan=NaN, inf=Infinity, nzero=0 Substitution numbers
s=${s}, n=${n}, nul=${nul}, supp=${supp} { s: "Hello, World!", n: "42", supp: "\ud800\udc00", nul: "\u0000" } s=Hello, World!, n=42, nul=�, supp=𐀀 Substitution strings
orphans=${orphans} { orphans: "\ud800-\udc00" } orphans=�-� Substitution non-unicode strings
t=${t}, f=${f} { t: true, f: false } t=true, f=false Substitution booleans
s=${s}, o=${o}, thunk=${thunk}, a=${a} { s: new String("foo"), o: { toString: function () { return "[Obj]" } }, thunk: function () { return "Hello, World!"; }, a: [1, 2, 3] } s=foo, o=[Obj], thunk=Hello, World!, a=1,2,3 Substitution objects
Hello, ${x}! { x: { toString: function () { return "string"; }, valueOf: function (typeHint) { return "value"; } } } Hello, string! toString vs valueOf
i++=${i++}, i++=${i++}, readOnce=${readOnce}, counter=${counter}, counter=${counter} { i: 0, readOnce: (function () { var run = false; return function() { if (run) throw Error(); run = true; return 42; }; })(), counter: (function () { var i = 0; return function () { return ++i; }; })() } i++=0, i++=1, readOnce=42, counter=1, counter=2 Substitution evaluation happens once and in order
<div title="${breaker}">${breaker}</div> { breaker: "-->]]></script></style></textarea>\" ' ) >" }
-->]]></script></style></textarea>" ' ) >
Substitution autoescaped
<b>{{if sayHello}}Hello{{else}}Goodbye{{/if}}</b>, ${world}! { sayHello: true, world: "Cincinatti" } Hello, Cincinatti! If branch taken
<b>{{if sayHello}}Hello{{else}}Goodbye{{/if}}</b>, ${world}! { sayHello: false, world: function () { return "Cleveland"; }} Goodbye, Cleveland! Else branch taken
<b>{{if sayHello}}Hello{{/if}}</b>, ${world}! { sayHello: true, world: "Cincinatti" } Hello, Cincinatti! If branch taken no else
<b>{{if sayHello}}Hello{{/if}}</b>, ${world}! { sayHello: false, world: function () { return "Cleveland"; }} , Cleveland! No else branch
<b>{{if sayHello}}Hello{{else sayGoodbye}}Goodbye{{/if}}</b>, ${world}! { sayHello: false, sayGoodbye: true, world: "Cincinatti" } Goodbye, Cincinatti! Condition in else taken
<b>{{if sayHello}}Hello{{else sayGoodbye}}Goodbye{{/if}}</b>, ${world}! { sayHello: false, sayGoodbye: false, world: "Cincinatti" } , Cincinatti! Condition in else not taken
<b>{{if sayHello}}Hello{{else}}Goodbye{{/if}}</b>, ${world}! { sayHello: 1, world: "Cincinatti" } Hello, Cincinatti! If branch taken based on truthy value
<b>{{if sayHello}}Hello{{else}}Goodbye{{/if}}</b>, ${world}! { sayHello: 0, world: function () { return "Cleveland"; }} Goodbye, Cleveland! Else branch taken based on falsey value
<ul>{{each arr}}<li value=\"${$index - -1}\">${$value}{{/each}}</ul> { arr: ["One", "Two"] }
  • One
  • Two
Loop over array
<ul>{{each arr}}<li value=\"${$index - -1}\">${$value}{{/each}}</ul> { arr: [] }
    Loop over empty
    <ul>{{each(i) arr}}<li value=\"${i - -1}\">${$value}{{/each}}</ul> { arr: ["One", "Two"] }
    • One
    • Two
    Loop with custom key variable name
    <ul>{{each(k, v) arr}}<li value=\"${k - -1}\">${v}{{/each}}</ul> { arr: ["One", "Two"] }
    • One
    • Two
    Loop with custom variable names
    <table>{{each(k,v) obj}}<tr><td>${k}</td><td>${v}</td></tr>{{/each}}</table> { obj: { "foo": "bar", "baz": "boo" } }
    foobar
    bazboo
    Loop over associative array.
    <script type="text/x-jquery-tmpl" id="main"> this x=${x}, {{tmpl "#that"}} </script> <script type="text/x-jquery-tmpl" id="that"> that x=(${x}) </script> { x: "x" } this x=x, that x=(x) Template Calls
    {{tmpl({ x, y }) "#that"}} { x: "x" } Error:Invalid {{tmpl}} content: ({ x, y }) "#that" Invalid Template Param
    {{tmpl({ x: y }) "#that}} { x: "x" } Error:Invalid {{tmpl}} content: ({ x: y }) "#that Invalid Template Selector
    <script type="text/x-jquery-tmpl" id="main"> this x=${x}, {{tmpl({x: "y"}) "#that"}} </script> <script type="text/x-jquery-tmpl" id="that"> that x=(${x}) </script> { x: "x" } this x=x, that x=(y) Call with different data
    <script type="text/x-jquery-tmpl" id="main"> this x=${++x}, {{tmpl({ x: ++x }) "#that"}} </script> <script type="text/x-jquery-tmpl" id="that"> that x=(${++x}) </script> { x: 0 } this x=1, that x=(3) Template Order of Evaluation
    ${1}${1} {} 11 String concatenation of adjacent subs
    ${1 + 1} {} 2 Arithmetic in substitution
    a[0].b[cStr]=${a[0].b[cStr]} { a: [{ b: { c: 42 } }], cStr: "c" } a[0].b[cStr]=42 Member expressions
    undef=${x.undef} { x: {} } undef= Undefined member
    {{if n < 0}}-{{tmpl({ n: -n }) "main"}}{{else n >= 1}}.{{tmpl({n: n - 1}) "main"}}{{/if}} { n: -3 } -... Recursive template
    {{if typeof notDefinedInDataMap !== "undefined"}}${notDefinedInDataMap}{{else}}none{{/if}} {} none Unreached variable need not be defined
    {{if cond}}foo{{else}} {} Error:Unclosed block directives if in {{if cond}}foo{{else}} Missing {{/if}} error message
    {{if cond}}foo{{else}}bar{{else}}boo{{/if}} {} Error:{{else}} without condition must be last:{{if cond}}foo{{else}}bar{{else}}boo{{/if}} Misplaced {{else}}
    {{if }}foo{{/if}} {} Error:{{if}} missing condition:{{if }}foo{{/if}} Missing {{if}} condition
    All nature is but art {{unknown}} to thee. {} Error:I do not know how to compile {{unknown}} Unknown directive name
    ${i + (j} { i: 1, j: 2 } Error:Invalid template substitution: i + (j Catch obviously broken expressions early.
    {{= String()) } { (String()}} { String: function () { return "String!"; } } Error:Invalid template substitution: String()) } { (String() Unbracketing and rebracketing illegal.
    ${arguments[0]} { arguments: [42] } 42 "arguments" as a data key
    ${arguments[0]} {} [object Object] "arguments" not present as a data key
    ${delete arguments[0], arguments[0]} {} "arguments[0]" not present
    ${$data.arguments = 42}, ${arguments} {} 42, 42 "arguments" present as a data key
    ${$view.foo} {}, { foo: 42 } 42 Template options available
    <script type="text/x-jquery-tmpl" id="main"> ${$view.foo},{{tmpl({ "$view": { foo: "data" } }) "#t"}},${$view.foo} </script> <script type="text/x-jquery-tmpl" id="t">${$view.foo}</script> {}, { foo: "option" } option,data,option Data masking template options
    <script type="text/x-jquery-tmpl" id="main"> ${$view.foo},{{tmpl(null,{ "foo": "option2" }) "#t"}},${$view.foo} </script> <script type="text/x-jquery-tmpl" id="t">${$view.foo}</script> {}, { foo: "option" } option,option2,option Passing options
    ${ob === $data} window.ob={} true Maintain data identity