diff --git a/spec/basic.js b/spec/basic.js index 1e4ce2b6b..4c7afb706 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -15,15 +15,19 @@ describe('basic context', function() { expectTemplate('\\{{foo}}') .withInput({ foo: 'food' }) .toCompileTo('{{foo}}'); + expectTemplate('content \\{{foo}}') .withInput({ foo: 'food' }) .toCompileTo('content {{foo}}'); + expectTemplate('\\\\{{foo}}') .withInput({ foo: 'food' }) .toCompileTo('\\food'); + expectTemplate('content \\\\{{foo}}') .withInput({ foo: 'food' }) .toCompileTo('content \\food'); + expectTemplate('\\\\ {{foo}}') .withInput({ foo: 'food' }) .toCompileTo('\\\\ food'); @@ -65,14 +69,19 @@ describe('basic context', function() { .toCompileTo('Goodbye\ncruel\nworld!'); expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah'); + expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo( 'blah' ); + expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah'); + expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo( ' blah' ); + expectTemplate(' {{~! comment}} blah').toCompileTo(' blah'); + expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo( ' blah' ); @@ -104,13 +113,16 @@ describe('basic context', function() { num2: 0 }) .toCompileTo('num1: 42, num2: 0'); + expectTemplate('num: {{.}}') .withInput(0) .toCompileTo('num: 0'); + expectTemplate('num: {{num1/num2}}') .withInput({ num1: { num2: 0 } }) .toCompileTo('num: 0'); }); + it('false', function() { /* eslint-disable no-new-wrappers */ expectTemplate('val1: {{val1}}, val2: {{val2}}') @@ -119,9 +131,11 @@ describe('basic context', function() { val2: new Boolean(false) }) .toCompileTo('val1: false, val2: false'); + expectTemplate('val: {{.}}') .withInput(false) .toCompileTo('val: false'); + expectTemplate('val: {{val1/val2}}') .withInput({ val1: { val2: false } }) .toCompileTo('val: false'); @@ -132,6 +146,7 @@ describe('basic context', function() { val2: new Boolean(false) }) .toCompileTo('val1: false, val2: false'); + expectTemplate('val: {{{val1/val2}}}') .withInput({ val1: { val2: false } }) .toCompileTo('val: false'); @@ -152,6 +167,7 @@ describe('basic context', function() { } }) .toCompileTo('true true object'); + expectTemplate('{{undefined}}') .withInput({ undefined: function() { @@ -159,6 +175,7 @@ describe('basic context', function() { } }) .toCompileTo('undefined!'); + expectTemplate('{{null}}') .withInput({ null: function() { @@ -170,6 +187,7 @@ describe('basic context', function() { it('newlines', function() { expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); + expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); }); @@ -179,16 +197,20 @@ describe('basic context', function() { "text is escaped so that it doesn't get caught on single quotes" ) .toCompileTo("Awesome's"); + expectTemplate('Awesome\\') .withMessage("text is escaped so that the closing quote can't be ignored") .toCompileTo('Awesome\\'); + expectTemplate('Awesome\\\\ foo') .withMessage("text is escaped so that it doesn't mess up backslashes") .toCompileTo('Awesome\\\\ foo'); + expectTemplate('Awesome {{foo}}') .withInput({ foo: '\\' }) .withMessage("text is escaped so that it doesn't mess up backslashes") .toCompileTo('Awesome \\'); + expectTemplate(" ' ' ") .withMessage('double quotes never produce invalid javascript') .toCompileTo(" ' ' "); @@ -217,13 +239,12 @@ describe('basic context', function() { }); it("functions returning safestrings shouldn't be escaped", function() { - var hash = { - awesome: function() { - return new Handlebars.SafeString("&'\\<>"); - } - }; expectTemplate('{{awesome}}') - .withInput(hash) + .withInput({ + awesome: function() { + return new Handlebars.SafeString("&'\\<>"); + } + }) .withMessage("functions returning safestrings aren't escaped") .toCompileTo("&'\\<>"); }); @@ -237,6 +258,7 @@ describe('basic context', function() { }) .withMessage('functions are called and render their output') .toCompileTo('Awesome'); + expectTemplate('{{awesome}}') .withInput({ awesome: function() { @@ -259,6 +281,7 @@ describe('basic context', function() { .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); + it('pathed functions with context argument', function() { expectTemplate('{{bar.awesome frank}}') .withInput({ @@ -272,6 +295,7 @@ describe('basic context', function() { .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); + it('depthed functions with context argument', function() { expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') .withInput({ @@ -319,6 +343,7 @@ describe('basic context', function() { .withMessage('block functions are called with options') .toCompileTo('inner'); }); + it('pathed block functions without context argument', function() { expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') .withInput({ @@ -331,6 +356,7 @@ describe('basic context', function() { .withMessage('block functions are called with options') .toCompileTo('inner'); }); + it('depthed block functions without context argument', function() { expectTemplate( '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' @@ -350,10 +376,12 @@ describe('basic context', function() { .withInput({ 'foo-bar': 'baz' }) .withMessage('Paths can contain hyphens (-)') .toCompileTo('baz'); + expectTemplate('{{foo.foo-bar}}') .withInput({ foo: { 'foo-bar': 'baz' } }) .withMessage('Paths can contain hyphens (-)') .toCompileTo('baz'); + expectTemplate('{{foo/foo-bar}}') .withInput({ foo: { 'foo-bar': 'baz' } }) .withMessage('Paths can contain hyphens (-)') @@ -379,6 +407,7 @@ describe('basic context', function() { .withInput({ '@alan': { expression: 'beautiful' } }) .withMessage('Literal paths can be used') .toCompileTo('Goodbye beautiful world!'); + expectTemplate('Goodbye {{[foo bar]/expression}} world!') .withInput({ 'foo bar': { expression: 'beautiful' } }) .withMessage('Literal paths can be used') @@ -389,18 +418,23 @@ describe('basic context', function() { expectTemplate('Goodbye {{[foo bar]}} world!') .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); + expectTemplate('Goodbye {{"foo bar"}} world!') .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); + expectTemplate("Goodbye {{'foo bar'}} world!") .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); + expectTemplate('Goodbye {{"foo[bar"}} world!') .withInput({ 'foo[bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); + expectTemplate('Goodbye {{"foo\'bar"}} world!') .withInput({ "foo'bar": 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); + expectTemplate("Goodbye {{'foo\"bar'}} world!") .withInput({ 'foo"bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); @@ -417,25 +451,22 @@ describe('basic context', function() { expectTemplate('{{person/name}}') .withInput({ person: { name: null } }) .toCompileTo(''); + expectTemplate('{{person/name}}') .withInput({ person: {} }) .toCompileTo(''); }); it('this keyword in paths', function() { - var string = '{{#goodbyes}}{{this}}{{/goodbyes}}'; - var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') + .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .withMessage('This keyword in paths evaluates to current context') .toCompileTo('goodbyeGoodbyeGOODBYE'); - string = '{{#hellos}}{{this/text}}{{/hellos}}'; - hash = { - hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') + .withInput({ + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + }) .withMessage('This keyword evaluates in more complex paths') .toCompileTo('helloHelloHELLO'); }); @@ -449,6 +480,7 @@ describe('basic context', function() { expectTemplate('{{[this]}}') .withInput({ this: 'bar' }) .toCompileTo('bar'); + expectTemplate('{{text/[this]}}') .withInput({ text: { this: 'bar' } }) .toCompileTo('bar'); @@ -460,28 +492,27 @@ describe('basic context', function() { return 'bar ' + value; } }; - var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}'; - var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }; - expectTemplate(string) - .withInput(hash) + + expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}') + .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .withHelpers(helpers) .withMessage('This keyword in paths evaluates to current context') .toCompileTo('bar goodbyebar Goodbyebar GOODBYE'); - string = '{{#hellos}}{{foo this/text}}{{/hellos}}'; - hash = { - hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}') + .withInput({ + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + }) .withHelpers(helpers) .withMessage('This keyword evaluates in more complex paths') .toCompileTo('bar hellobar Hellobar HELLO'); }); it('this keyword nested inside helpers param', function() { - var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; - expectTemplate(string).toThrow(Error, 'Invalid path: text/this - 1:17'); + expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( + Error, + 'Invalid path: text/this - 1:17' + ); expectTemplate('{{foo [this]}}') .withInput({ @@ -491,6 +522,7 @@ describe('basic context', function() { this: 'bar' }) .toCompileTo('bar'); + expectTemplate('{{foo text/[this]}}') .withInput({ foo: function(value) { @@ -503,9 +535,11 @@ describe('basic context', function() { it('pass string literals', function() { expectTemplate('{{"foo"}}').toCompileTo(''); + expectTemplate('{{"foo"}}') .withInput({ foo: 'bar' }) .toCompileTo('bar'); + expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') .withInput({ foo: ['bar', 'baz'] @@ -515,13 +549,17 @@ describe('basic context', function() { it('pass number literals', function() { expectTemplate('{{12}}').toCompileTo(''); + expectTemplate('{{12}}') .withInput({ '12': 'bar' }) .toCompileTo('bar'); + expectTemplate('{{12.34}}').toCompileTo(''); + expectTemplate('{{12.34}}') .withInput({ '12.34': 'bar' }) .toCompileTo('bar'); + expectTemplate('{{12.34 1}}') .withInput({ '12.34': function(arg) { @@ -533,27 +571,26 @@ describe('basic context', function() { it('pass boolean literals', function() { expectTemplate('{{true}}').toCompileTo(''); + expectTemplate('{{true}}') .withInput({ '': 'foo' }) .toCompileTo(''); + expectTemplate('{{false}}') .withInput({ false: 'foo' }) .toCompileTo('foo'); }); it('should handle literals in subexpression', function() { - var helpers = { - foo: function(arg) { - return arg; - } - }; expectTemplate('{{foo (false)}}') .withInput({ false: function() { return 'bar'; } }) - .withHelpers(helpers) + .withHelper('foo', function(arg) { + return arg; + }) .toCompileTo('bar'); }); }); diff --git a/spec/blocks.js b/spec/blocks.js index ab16fd9d3..f15655428 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -1,12 +1,16 @@ describe('blocks', function() { it('array', function() { var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; + expectTemplate(string) - .withInput(hash) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('Arrays iterate over the contents when not empty') .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); @@ -20,40 +24,49 @@ describe('blocks', function() { }); it('array without data', function() { - var string = - '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withCompileOptions({ compat: false }) .toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'); }); it('array with @index', function() { - var string = - '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); it('empty block', function() { var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; + expectTemplate(string) - .withInput(hash) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('Arrays iterate over the contents when not empty') .toCompileTo('cruel world!'); @@ -67,14 +80,15 @@ describe('blocks', function() { }); it('block with complex lookup', function() { - var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}'; - var hash = { - name: 'Alan', - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }] - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ] + }) .withMessage( 'Templates can access variables in contexts up the stack with relative path syntax' ) @@ -84,80 +98,72 @@ describe('blocks', function() { }); it('multiple blocks with complex lookup', function() { - var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}'; - var hash = { - name: 'Alan', - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }] - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ] + }) .toCompileTo('AlanAlanAlanAlanAlanAlan'); }); it('block with complex lookup using nested context', function() { - var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'; - - expectTemplate(string).toThrow(Error); + expectTemplate( + '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}' + ).toThrow(Error); }); it('block with deep nested complex lookup', function() { - var string = - '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'; - var hash = { - omg: 'OMG!', - outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: 'OMG!', + outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] + }) .toCompileTo('Goodbye cruel sad OMG!'); }); it('works with cached blocks', function() { - var string = - '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'; - var compileOptions = { data: false }; - - var input = { - person: [ - { first: 'Alan', last: 'Johnson' }, - { first: 'Alan', last: 'Johnson' } - ] - }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withInput(input) + expectTemplate( + '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}' + ) + .withCompileOptions({ data: false }) + .withInput({ + person: [ + { first: 'Alan', last: 'Johnson' }, + { first: 'Alan', last: 'Johnson' } + ] + }) .toCompileTo('Alan JohnsonAlan Johnson'); }); describe('inverted sections', function() { it('inverted sections with unset value', function() { - var string = - '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'; - var hash = {}; - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) .withMessage("Inverted section rendered when value isn't set.") .toCompileTo('Right On!'); }); it('inverted section with false value', function() { - var string = - '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'; - var hash = { goodbyes: false }; - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withInput({ goodbyes: false }) .withMessage('Inverted section rendered when value is false.') .toCompileTo('Right On!'); }); it('inverted section with empty set', function() { - var string = - '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'; - var hash = { goodbyes: [] }; - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withInput({ goodbyes: [] }) .withMessage('Inverted section rendered when value is empty set.') .toCompileTo('Right On!'); }); @@ -167,21 +173,25 @@ describe('blocks', function() { .withInput({ none: 'No people' }) .toCompileTo('No people'); }); + it('chained inverted sections', function() { expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}') .withInput({ none: 'No people' }) .toCompileTo('No people'); + expectTemplate( '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}' ) .withInput({ none: 'No people' }) .toCompileTo('No people'); + expectTemplate( '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}' ) .withInput({ none: 'No people' }) .toCompileTo('No people'); }); + it('chained inverted sections with mismatch', function() { expectTemplate( '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' @@ -203,35 +213,42 @@ describe('blocks', function() { expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') .withInput({ none: 'No people' }) .toCompileTo('No people\n'); + expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n') .withInput({ none: 'No people' }) .toCompileTo('No people\n'); + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') .withInput({ none: 'No people' }) .toCompileTo('No people\n'); }); + it('block standalone else sections can be disabled', function() { expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') .withInput({ none: 'No people' }) .withCompileOptions({ ignoreStandalone: true }) .toCompileTo('\nNo people\n\n'); + expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n') .withInput({ none: 'No people' }) .withCompileOptions({ ignoreStandalone: true }) .toCompileTo('\nNo people\n\n'); }); + it('block standalone chained else sections', function() { expectTemplate( '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n' ) .withInput({ none: 'No people' }) .toCompileTo('No people\n'); + expectTemplate( '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n' ) .withInput({ none: 'No people' }) .toCompileTo('No people\n'); }); + it('should handle nesting', function() { expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') .withInput({ @@ -243,39 +260,34 @@ describe('blocks', function() { describe('compat mode', function() { it('block with deep recursive lookup lookup', function() { - var string = - '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'; - var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}' + ) + .withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }) .withCompileOptions({ compat: true }) .toCompileTo('Goodbye cruel OMG!'); }); it('block with deep recursive pathed lookup', function() { - var string = - '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; - var hash = { - omg: { yes: 'OMG!' }, - outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: { yes: 'OMG!' }, + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + }) .withCompileOptions({ compat: true }) .toCompileTo('Goodbye cruel OMG!'); }); + it('block with missed recursive lookup', function() { - var string = - '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; - var hash = { - omg: { no: 'OMG!' }, - outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: { no: 'OMG!' }, + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + }) .withCompileOptions({ compat: true }) .toCompileTo('Goodbye cruel '); }); @@ -283,145 +295,109 @@ describe('blocks', function() { describe('decorators', function() { it('should apply mustache decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn) { + }) + .withDecorator('decorator', function(fn) { fn.run = 'success'; return fn; - } - }; - expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') - .withHelpers(helpers) - .withDecorators(decorators) + }) .toCompileTo('success'); }); + it('should apply allow undefined return', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') + .withHelper('helper', function(options) { return options.fn() + options.fn.run; - } - }; - var decorators = { - decorator: function(fn) { + }) + .withDecorator('decorator', function(fn) { fn.run = 'cess'; - } - }; - expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') - .withHelpers(helpers) - .withDecorators(decorators) + }) .toCompileTo('success'); }); it('should apply block decorators', function() { - var helpers = { - helper: function(options) { - return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { - fn.run = options.fn(); - return fn; - } - }; expectTemplate( '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}' ) - .withHelpers(helpers) - .withDecorators(decorators) + .withHelper('helper', function(options) { + return options.fn.run; + }) + .withDecorator('decorator', function(fn, props, container, options) { + fn.run = options.fn(); + return fn; + }) .toCompileTo('success'); }); + it('should support nested decorators', function() { - var helpers = { - helper: function(options) { - return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { - fn.run = options.fn.nested + options.fn(); - return fn; - }, - nested: function(fn, props, container, options) { - props.nested = options.fn(); - } - }; expectTemplate( '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' ) - .withHelpers(helpers) - .withDecorators(decorators) + .withHelper('helper', function(options) { + return options.fn.run; + }) + .withDecorators({ + decorator: function(fn, props, container, options) { + fn.run = options.fn.nested + options.fn(); + return fn; + }, + nested: function(fn, props, container, options) { + props.nested = options.fn(); + } + }) .toCompileTo('success'); }); it('should apply multiple decorators', function() { - var helpers = { - helper: function(options) { - return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { - fn.run = (fn.run || '') + options.fn(); - return fn; - } - }; expectTemplate( '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' ) - .withHelpers(helpers) - .withDecorators(decorators) + .withHelper('helper', function(options) { + return options.fn.run; + }) + .withDecorator('decorator', function(fn, props, container, options) { + fn.run = (fn.run || '') + options.fn(); + return fn; + }) .toCompileTo('success'); }); it('should access parent variables', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { + }) + .withDecorator('decorator', function(fn, props, container, options) { fn.run = options.args; return fn; - } - }; - expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') - .withHelpers(helpers) - .withDecorators(decorators) + }) .withInput({ foo: 'success' }) .toCompileTo('success'); }); + it('should work with root program', function() { var run; - var decorators = { - decorator: function(fn, props, container, options) { + expectTemplate('{{*decorator "success"}}') + .withDecorator('decorator', function(fn, props, container, options) { equals(options.args[0], 'success'); run = true; return fn; - } - }; - expectTemplate('{{*decorator "success"}}') - .withDecorators(decorators) + }) .withInput({ foo: 'success' }) .toCompileTo(''); equals(run, true); }); + it('should fail when accessing variables from root', function() { var run; - var decorators = { - decorator: function(fn, props, container, options) { + expectTemplate('{{*decorator foo}}') + .withDecorator('decorator', function(fn, props, container, options) { equals(options.args[0], undefined); run = true; return fn; - } - }; - expectTemplate('{{*decorator foo}}') - .withDecorators(decorators) + }) .withInput({ foo: 'fail' }) .toCompileTo(''); equals(run, true); @@ -455,6 +431,7 @@ describe('blocks', function() { equals(handlebarsEnv.decorators.foo, undefined); equals(handlebarsEnv.decorators.bar, undefined); }); + it('fails with multiple and args', function() { shouldThrow( function() { diff --git a/spec/builtins.js b/spec/builtins.js index b54c5ca3a..bd44d8b74 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -2,6 +2,7 @@ describe('builtin helpers', function() { describe('#if', function() { it('if', function() { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; + expectTemplate(string) .withInput({ goodbye: true, @@ -9,6 +10,7 @@ describe('builtin helpers', function() { }) .withMessage('if with boolean argument shows the contents when true') .toCompileTo('GOODBYE cruel world!'); + expectTemplate(string) .withInput({ goodbye: 'dummy', @@ -16,6 +18,7 @@ describe('builtin helpers', function() { }) .withMessage('if with string argument shows the contents') .toCompileTo('GOODBYE cruel world!'); + expectTemplate(string) .withInput({ goodbye: false, @@ -25,10 +28,12 @@ describe('builtin helpers', function() { 'if with boolean argument does not show the contents when false' ) .toCompileTo('cruel world!'); + expectTemplate(string) .withInput({ world: 'world' }) .withMessage('if with undefined does not show the contents') .toCompileTo('cruel world!'); + expectTemplate(string) .withInput({ goodbye: ['foo'], @@ -36,6 +41,7 @@ describe('builtin helpers', function() { }) .withMessage('if with non-empty array shows the contents') .toCompileTo('GOODBYE cruel world!'); + expectTemplate(string) .withInput({ goodbye: [], @@ -43,6 +49,7 @@ describe('builtin helpers', function() { }) .withMessage('if with empty array does not show the contents') .toCompileTo('cruel world!'); + expectTemplate(string) .withInput({ goodbye: 0, @@ -50,6 +57,7 @@ describe('builtin helpers', function() { }) .withMessage('if with zero does not show the contents') .toCompileTo('cruel world!'); + expectTemplate( '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!' ) @@ -63,6 +71,7 @@ describe('builtin helpers', function() { it('if with function argument', function() { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; + expectTemplate(string) .withInput({ goodbye: function() { @@ -74,6 +83,7 @@ describe('builtin helpers', function() { 'if with function shows the contents when function returns true' ) .toCompileTo('GOODBYE cruel world!'); + expectTemplate(string) .withInput({ goodbye: function() { @@ -85,6 +95,7 @@ describe('builtin helpers', function() { 'if with function shows the contents when function returns string' ) .toCompileTo('GOODBYE cruel world!'); + expectTemplate(string) .withInput({ goodbye: function() { @@ -96,6 +107,7 @@ describe('builtin helpers', function() { 'if with function does not show the contents when returns false' ) .toCompileTo('cruel world!'); + expectTemplate(string) .withInput({ goodbye: function() { @@ -110,9 +122,9 @@ describe('builtin helpers', function() { }); it('should not change the depth list', function() { - var string = - '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; - expectTemplate(string) + expectTemplate( + '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' + ) .withInput({ foo: { goodbye: true }, world: 'world' @@ -123,8 +135,7 @@ describe('builtin helpers', function() { describe('#with', function() { it('with', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - expectTemplate(string) + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', @@ -133,9 +144,9 @@ describe('builtin helpers', function() { }) .toCompileTo('Alan Johnson'); }); + it('with with function argument', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - expectTemplate(string) + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') .withInput({ person: function() { return { @@ -146,14 +157,15 @@ describe('builtin helpers', function() { }) .toCompileTo('Alan Johnson'); }); + it('with with else', function() { - var string = - '{{#with person}}Person is present{{else}}Person is not present{{/with}}'; - expectTemplate(string).toCompileTo('Person is not present'); + expectTemplate( + '{{#with person}}Person is present{{else}}Person is not present{{/with}}' + ).toCompileTo('Person is not present'); }); + it('with provides block parameter', function() { - var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}'; - expectTemplate(string) + expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', @@ -162,6 +174,7 @@ describe('builtin helpers', function() { }) .toCompileTo('Alan Johnson'); }); + it('works when data is disabled', function() { expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', last: 'Johnson' } }) @@ -179,20 +192,21 @@ describe('builtin helpers', function() { it('each', function() { var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; + expectTemplate(string) - .withInput(hash) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage( 'each with array argument iterates over the contents when not empty' ) .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + expectTemplate(string) .withInput({ goodbyes: [], @@ -203,32 +217,28 @@ describe('builtin helpers', function() { }); it('each without data', function() { - var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withRuntimeOptions({ data: false }) .withCompileOptions({ data: false }) .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - hash = { goodbyes: 'cruel', world: 'world' }; expectTemplate('{{#each .}}{{.}}{{/each}}') - .withInput(hash) + .withInput({ goodbyes: 'cruel', world: 'world' }) .withRuntimeOptions({ data: false }) .withCompileOptions({ data: false }) .toCompileTo('cruelworld'); }); it('each without context', function() { - var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - expectTemplate(string) + expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') .withInput(undefined) .toCompileTo('cruel !'); }); @@ -258,6 +268,7 @@ describe('builtin helpers', function() { true, 'each with object argument iterates over the contents when not empty' ); + expectTemplate(string) .withInput({ goodbyes: {}, @@ -267,37 +278,33 @@ describe('builtin helpers', function() { }); it('each with @index', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); it('each with nested @index', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @index variable is used') .toCompileTo( '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!' @@ -305,70 +312,62 @@ describe('builtin helpers', function() { }); it('each with block params', function() { - var string = - '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }], + world: 'world' + }) .toCompileTo( '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' ); }); it('each object with @index', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { - a: { text: 'goodbye' }, - b: { text: 'Goodbye' }, - c: { text: 'GOODBYE' } - }, - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { + a: { text: 'goodbye' }, + b: { text: 'Goodbye' }, + c: { text: 'GOODBYE' } + }, + world: 'world' + }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); it('each with @first', function() { - var string = - '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @first variable is used') .toCompileTo('goodbye! cruel world!'); }); it('each with nested @first', function() { - var string = - '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @first variable is used') .toCompileTo( '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!' @@ -376,65 +375,57 @@ describe('builtin helpers', function() { }); it('each object with @first', function() { - var string = - '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, + world: 'world' + }) .withMessage('The @first variable is used') .toCompileTo('goodbye! cruel world!'); }); it('each with @last', function() { - var string = - '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @last variable is used') .toCompileTo('GOODBYE! cruel world!'); }); it('each object with @last', function() { - var string = - '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, + world: 'world' + }) .withMessage('The @last variable is used') .toCompileTo('Goodbye! cruel world!'); }); it('each with nested @last', function() { - var string = - '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) .withMessage('The @last variable is used') .toCompileTo( '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!' @@ -443,22 +434,23 @@ describe('builtin helpers', function() { it('each with function argument', function() { var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: function() { - return [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ]; - }, - world: 'world' - }; + expectTemplate(string) - .withInput(hash) + .withInput({ + goodbyes: function() { + return [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ]; + }, + world: 'world' + }) .withMessage( 'each with array function argument iterates over the contents when not empty' ) .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + expectTemplate(string) .withInput({ goodbyes: [], @@ -471,29 +463,26 @@ describe('builtin helpers', function() { }); it('each object when last key is an empty string', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { - a: { text: 'goodbye' }, - b: { text: 'Goodbye' }, - '': { text: 'GOODBYE' } - }, - world: 'world' - }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { + a: { text: 'goodbye' }, + b: { text: 'Goodbye' }, + '': { text: 'GOODBYE' } + }, + world: 'world' + }) .withMessage('Empty string key is not skipped') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); it('data passed to helpers', function() { - var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'; - var hash = { letters: ['a', 'b', 'c'] }; - - expectTemplate(string) - .withInput(hash) + expectTemplate( + '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' + ) + .withInput({ letters: ['a', 'b', 'c'] }) .withMessage('should output data') .withRuntimeOptions({ data: { @@ -504,8 +493,7 @@ describe('builtin helpers', function() { }); it('each on implicit context', function() { - var string = '{{#each}}{{text}}! {{/each}}cruel world!'; - expectTemplate(string).toThrow( + expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( handlebarsEnv.Exception, 'Must pass iterator to #each' ); @@ -539,12 +527,14 @@ describe('builtin helpers', function() { ]); var goodbyesEmpty = new Iterable([]); var hash = { goodbyes: goodbyes, world: 'world' }; + expectTemplate(string) .withInput(hash) .withMessage( 'each with array argument iterates over the contents when not empty' ) .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + expectTemplate(string) .withInput({ goodbyes: goodbyesEmpty, @@ -577,43 +567,37 @@ describe('builtin helpers', function() { }); it('should call logger at default level', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; - var levelArg, logArg; handlebarsEnv.log = function(level, arg) { levelArg = level; logArg = arg; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) .withMessage('log should not display') .toCompileTo(''); equals(1, levelArg, 'should call log with 1'); equals('whee', logArg, "should call log with 'whee'"); }); - it('should call logger at data level', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; + it('should call logger at data level', function() { var levelArg, logArg; handlebarsEnv.log = function(level, arg) { levelArg = level; logArg = arg; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) .withRuntimeOptions({ data: { level: '03' } }) .withCompileOptions({ data: true }) .toCompileTo(''); equals('03', levelArg); equals('whee', logArg); }); + it('should output to info', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.info = function(info) { @@ -629,14 +613,13 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); equals(true, called); }); + it('should log at data level', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -645,17 +628,16 @@ describe('builtin helpers', function() { console.error = $error; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) .withRuntimeOptions({ data: { level: '03' } }) .withCompileOptions({ data: true }) .toCompileTo(''); equals(true, called); }); + it('should handle missing logger', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }, - called = false; + var called = false; console.error = undefined; console.log = function(log) { @@ -664,8 +646,8 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) .withRuntimeOptions({ data: { level: '03' } }) .withCompileOptions({ data: true }) .toCompileTo(''); @@ -698,6 +680,7 @@ describe('builtin helpers', function() { .toCompileTo(''); equals(true, called); }); + it('should handle hash log levels', function() { var string = '{{log blah level="error"}}'; var hash = { blah: 'whee' }; @@ -713,9 +696,8 @@ describe('builtin helpers', function() { .toCompileTo(''); equals(true, called); }); + it('should handle hash log levels', function() { - var string = '{{log blah level="debug"}}'; - var hash = { blah: 'whee' }; var called = false; console.info = console.log = console.error = console.debug = function() { @@ -723,14 +705,13 @@ describe('builtin helpers', function() { console.info = console.log = console.error = console.debug = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah level="debug"}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); equals(false, called); }); + it('should pass multiple log arguments', function() { - var string = '{{log blah "foo" 1}}'; - var hash = { blah: 'whee' }; var called; console.info = console.log = function(log1, log2, log3) { @@ -741,15 +722,13 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log blah "foo" 1}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); equals(true, called); }); it('should pass zero log arguments', function() { - var string = '{{log}}'; - var hash = { blah: 'whee' }; var called; console.info = console.log = function() { @@ -758,8 +737,8 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); expect(called).to.be.true(); }); @@ -768,19 +747,14 @@ describe('builtin helpers', function() { describe('#lookup', function() { it('should lookup arbitrary content', function() { - var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}', - hash = { goodbyes: [0, 1], data: ['foo', 'bar'] }; - - expectTemplate(string) - .withInput(hash) + expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}') + .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) .toCompileTo('foobar'); }); - it('should not fail on undefined value', function() { - var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}', - hash = { goodbyes: [0, 1], data: ['foo', 'bar'] }; - expectTemplate(string) - .withInput(hash) + it('should not fail on undefined value', function() { + expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}') + .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) .toCompileTo(''); }); }); diff --git a/spec/data.js b/spec/data.js index 5589eb0b0..8a126d2b2 100644 --- a/spec/data.js +++ b/spec/data.js @@ -1,16 +1,10 @@ describe('data', function() { it('passing in data to a compiled function that expects data - works with helpers', function() { - var string = '{{hello}}'; - - var helpers = { - hello: function(options) { - return options.data.adjective + ' ' + this.noun; - } - }; - - expectTemplate(string) + expectTemplate('{{hello}}') .withCompileOptions({ data: true }) - .withHelpers(helpers) + .withHelper('hello', function(options) { + return options.data.adjective + ' ' + this.noun; + }) .withRuntimeOptions({ data: { adjective: 'happy' } }) .withInput({ noun: 'cat' }) .withMessage('Data output by helper') @@ -25,9 +19,6 @@ describe('data', function() { }); it('deep @foo triggers automatic top-level data', function() { - var string = - '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'; - var helpers = Handlebars.createFrame(handlebarsEnv.helpers); helpers.let = function(options) { @@ -41,7 +32,9 @@ describe('data', function() { return options.fn(this, { data: frame }); }; - expectTemplate(string) + expectTemplate( + '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}' + ) .withInput({ foo: true }) .withHelpers(helpers) .withMessage('Automatic data was triggered') @@ -49,107 +42,82 @@ describe('data', function() { }); it('parameter data can be looked up via @foo', function() { - var string = '{{hello @world}}'; - var helpers = { - hello: function(noun) { - return 'Hello ' + noun; - } - }; - - expectTemplate(string) + expectTemplate('{{hello @world}}') .withRuntimeOptions({ data: { world: 'world' } }) - .withHelpers(helpers) + .withHelper('hello', function(noun) { + return 'Hello ' + noun; + }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); it('hash values can be looked up via @foo', function() { - var string = '{{hello noun=@world}}'; - var helpers = { - hello: function(options) { - return 'Hello ' + options.hash.noun; - } - }; - - expectTemplate(string) + expectTemplate('{{hello noun=@world}}') .withRuntimeOptions({ data: { world: 'world' } }) - .withHelpers(helpers) + .withHelper('hello', function(options) { + return 'Hello ' + options.hash.noun; + }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); it('nested parameter data can be looked up via @foo.bar', function() { - var string = '{{hello @world.bar}}'; - var helpers = { - hello: function(noun) { - return 'Hello ' + noun; - } - }; - - expectTemplate(string) + expectTemplate('{{hello @world.bar}}') .withRuntimeOptions({ data: { world: { bar: 'world' } } }) - .withHelpers(helpers) + .withHelper('hello', function(noun) { + return 'Hello ' + noun; + }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); it('nested parameter data does not fail with @world.bar', function() { - var string = '{{hello @world.bar}}'; - var helpers = { - hello: function(noun) { - return 'Hello ' + noun; - } - }; - - expectTemplate(string) + expectTemplate('{{hello @world.bar}}') .withRuntimeOptions({ data: { foo: { bar: 'world' } } }) - .withHelpers(helpers) + .withHelper('hello', function(noun) { + return 'Hello ' + noun; + }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello undefined'); }); it('parameter data throws when using complex scope references', function() { - var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'; - - expectTemplate(string).toThrow(Error); + expectTemplate( + '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' + ).toThrow(Error); }); it('data can be functions', function() { - var string = '{{@hello}}'; - var runtimeOptions = { - data: { - hello: function() { - return 'hello'; + expectTemplate('{{@hello}}') + .withRuntimeOptions({ + data: { + hello: function() { + return 'hello'; + } } - } - }; - - expectTemplate(string) - .withRuntimeOptions(runtimeOptions) + }) .toCompileTo('hello'); }); it('data can be functions with params', function() { - var string = '{{@hello "hello"}}'; - var runtimeOptions = { - data: { - hello: function(arg) { - return arg; + expectTemplate('{{@hello "hello"}}') + .withRuntimeOptions({ + data: { + hello: function(arg) { + return arg; + } } - } - }; - - expectTemplate(string) - .withRuntimeOptions(runtimeOptions) + }) .toCompileTo('hello'); }); it('data is inherited downstream', function() { - var string = - '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'; - var compileOptions = { data: true }; - var helpers = { - let: function(options) { + expectTemplate( + '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}' + ) + .withInput({ bar: { baz: 'hello world' } }) + .withCompileOptions({ data: true }) + .withHelper('let', function(options) { var frame = Handlebars.createFrame(options.data); for (var prop in options.hash) { if (prop in options.hash) { @@ -157,193 +125,117 @@ describe('data', function() { } } return options.fn(this, { data: frame }); - } - }; - - expectTemplate(string) - .withInput({ bar: { baz: 'hello world' } }) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + }) .withRuntimeOptions({ data: {} }) .withMessage('data variables are inherited downstream') .toCompileTo('2hello world1'); }); it('passing in data to a compiled function that expects data - works with helpers in partials', function() { - var string = '{{>myPartial}}'; - var compileOptions = { data: true }; - var partials = { myPartial: CompilerContext.compile('{{hello}}', { data: true }) }; - var helpers = { - hello: function(options) { - return options.data.adjective + ' ' + this.noun; - } - }; - - var input = { noun: 'cat' }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate('{{>myPartial}}') + .withCompileOptions({ data: true }) .withPartials(partials) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + .withHelper('hello', function(options) { + return options.data.adjective + ' ' + this.noun; + }) + .withInput({ noun: 'cat' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Data output by helper inside partial') .toCompileTo('happy cat'); }); it('passing in data to a compiled function that expects data - works with helpers and parameters', function() { - var string = '{{hello world}}'; - var compileOptions = { data: true }; - - var helpers = { - hello: function(noun, options) { + expectTemplate('{{hello world}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(noun, options) { return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : ''); - } - }; - - var input = { exclaim: true, world: 'world' }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true, world: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Data output by helper') .toCompileTo('happy world!'); }); it('passing in data to a compiled function that expects data - works with block helpers', function() { - var string = '{{#hello}}{{world}}{{/hello}}'; - var compileOptions = { - data: true - }; - - var helpers = { - hello: function(options) { + expectTemplate('{{#hello}}{{world}}{{/hello}}') + .withCompileOptions({ + data: true + }) + .withHelper('hello', function(options) { return options.fn(this); - }, - world: function(options) { + }) + .withHelper('world', function(options) { return options.data.adjective + ' world' + (this.exclaim ? '!' : ''); - } - }; - - var input = { exclaim: true }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Data output by helper') .toCompileTo('happy world!'); }); it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() { - var string = '{{#hello}}{{world ../zomg}}{{/hello}}'; - var compileOptions = { data: true }; - - var helpers = { - hello: function(options) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn({ exclaim: '?' }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); - } - }; - - var input = { exclaim: true, zomg: 'world' }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Data output by helper') .toCompileTo('happy world?'); }); it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() { - var string = '{{#hello}}{{world ../zomg}}{{/hello}}'; - var compileOptions = { data: true }; - - var helpers = { - hello: function(options) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.data.accessData + ' ' + options.fn({ exclaim: '?' }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); - } - }; - - var input = { exclaim: true, zomg: 'world' }; - var runtimeOptions = { data: { adjective: 'happy', accessData: '#win' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } }) .withMessage('Data output by helper') .toCompileTo('#win happy world?'); }); it('you can override inherited data when invoking a helper', function() { - var string = '{{#hello}}{{world zomg}}{{/hello}}'; - var compileOptions = { data: true }; - - var helpers = { - hello: function(options) { + expectTemplate('{{#hello}}{{world zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn( { exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } } ); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); - } - }; - - var input = { exclaim: true, zomg: 'planet' }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true, zomg: 'planet' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Overriden data output by helper') .toCompileTo('sad world?'); }); it('you can override inherited data when invoking a helper with depth', function() { - var string = '{{#hello}}{{world ../zomg}}{{/hello}}'; - var compileOptions = { data: true }; - - var helpers = { - hello: function(options) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); - } - }; - - var input = { exclaim: true, zomg: 'world' }; - var runtimeOptions = { data: { adjective: 'happy' } }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) - .withRuntimeOptions(runtimeOptions) + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) .withMessage('Overriden data output by helper') .toCompileTo('sad world?'); }); @@ -362,36 +254,30 @@ describe('data', function() { .withInput(input) .toCompileTo('hello'); }); + it('passed root values take priority', function() { - var string = '{{@root.foo}}'; - var runtimeOptions = { data: { root: { foo: 'hello' } } }; - expectTemplate(string) - .withRuntimeOptions(runtimeOptions) + expectTemplate('{{@root.foo}}') + .withRuntimeOptions({ data: { root: { foo: 'hello' } } }) .toCompileTo('hello'); }); }); describe('nesting', function() { it('the root context can be looked up via @root', function() { - var string = - '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'; - var input = { foo: 'hello' }; - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' + ) + .withInput({ foo: 'hello' }) + .withHelper('helper', function(options) { var frame = Handlebars.createFrame(options.data); frame.depth = options.data.depth + 1; return options.fn(this, { data: frame }); - } - }; - var runtimeOptions = { - data: { - depth: 0 - } - }; - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) - .withRuntimeOptions(runtimeOptions) + }) + .withRuntimeOptions({ + data: { + depth: 0 + } + }) .toCompileTo('2 1 0'); }); }); diff --git a/spec/helpers.js b/spec/helpers.js index 59960b384..5166d58d6 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -1,62 +1,44 @@ describe('helpers', function() { it('helper with complex lookup$', function() { - var string = '{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}'; - var hash = { - prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] - }; - var helpers = { - link: function(prefix) { + expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}') + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix) { return ( '' + this.text + '' ); - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('Goodbye'); }); it('helper for raw block gets raw content', function() { - var string = '{{{{raw}}}} {{test}} {{{{/raw}}}}'; - var hash = { test: 'hello' }; - var helpers = { - raw: function(options) { + expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}') + .withInput({ test: 'hello' }) + .withHelper('raw', function(options) { return options.fn(); - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .withMessage('raw block helper gets raw content') .toCompileTo(' {{test}} '); }); it('helper for raw block gets parameters', function() { - var string = '{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}'; - var hash = { test: 'hello' }; - var helpers = { - raw: function(a, b, c, options) { + expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}') + .withInput({ test: 'hello' }) + .withHelper('raw', function(a, b, c, options) { return options.fn() + a + b + c; - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .withMessage('raw block helper gets raw content') .toCompileTo(' {{test}} 123'); }); describe('raw block parsing (with identity helper-function)', function() { function runWithIdentityHelper(template, expected) { - var helpers = { - identity: function(options) { - return options.fn(); - } - }; expectTemplate(template) - .withHelpers(helpers) + .withHelper('identity', function(options) { + return options.fn(); + }) .toCompileTo(expected); } @@ -96,51 +78,42 @@ describe('helpers', function() { }); it('helper block with identical context', function() { - var string = '{{#goodbyes}}{{name}}{{/goodbyes}}'; - var hash = { name: 'Alan' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}') + .withInput({ name: 'Alan' }) + .withHelper('goodbyes', function(options) { var out = ''; var byes = ['Goodbye', 'goodbye', 'GOODBYE']; for (var i = 0, j = byes.length; i < j; i++) { out += byes[i] + ' ' + options.fn(this) + '! '; } return out; - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); }); + it('helper block with complex lookup expression', function() { - var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}'; - var hash = { name: 'Alan' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}') + .withInput({ name: 'Alan' }) + .withHelper('goodbyes', function(options) { var out = ''; var byes = ['Goodbye', 'goodbye', 'GOODBYE']; for (var i = 0, j = byes.length; i < j; i++) { out += byes[i] + ' ' + options.fn({}) + '! '; } return out; - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); }); it('helper with complex lookup and nested template', function() { - var string = - '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}'; - var hash = { - prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] - }; - var helpers = { - link: function(prefix, options) { + expectTemplate( + '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' + ) + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix, options) { return ( 'Goodbye'); }); it('helper with complex lookup and nested template in VM+Compiler', function() { - var string = - '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}'; - var hash = { - prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] - }; - var helpers = { - link: function(prefix, options) { + expectTemplate( + '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' + ) + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix, options) { return ( 'Goodbye'); }); + it('helper returning undefined value', function() { expectTemplate(' {{nothere}}') .withHelpers({ nothere: function() {} }) .toCompileTo(' '); + expectTemplate(' {{#nothere}}{{/nothere}}') .withHelpers({ nothere: function() {} @@ -197,55 +164,40 @@ describe('helpers', function() { }); it('block helper', function() { - var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; - - var input = { world: 'world' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!') + .withInput({ world: 'world' }) + .withHelper('goodbyes', function(options) { return options.fn({ text: 'GOODBYE' }); - } - }; - - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) + }) .withMessage('Block helper executed') .toCompileTo('GOODBYE! cruel world!'); }); it('block helper staying in the same context', function() { - var string = '{{#form}}

{{name}}

{{/form}}'; - - var input = { name: 'Yehuda' }; - var helpers = { - form: function(options) { + expectTemplate('{{#form}}

{{name}}

{{/form}}') + .withInput({ name: 'Yehuda' }) + .withHelper('form', function(options) { return '
' + options.fn(this) + '
'; - } - }; - - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) + }) .withMessage('Block helper executed with current context') .toCompileTo('

Yehuda

'); }); it('block helper should have context in this', function() { - var source = - ''; function link(options) { return '' + options.fn(this) + ''; } - var data = { - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 } - ] - }; - expectTemplate(source) - .withInput(data) - .withHelpers({ link: link }) + expectTemplate( + '' + ) + .withInput({ + people: [ + { name: 'Alan', id: 1 }, + { name: 'Yehuda', id: 2 } + ] + }) + .withHelper('link', link) .toCompileTo( '' ); @@ -256,58 +208,38 @@ describe('helpers', function() { }); it('block helper passing a new context', function() { - var string = '{{#form yehuda}}

{{name}}

{{/form}}'; - - var input = { yehuda: { name: 'Yehuda' } }; - var helpers = { - form: function(context, options) { + expectTemplate('{{#form yehuda}}

{{name}}

{{/form}}') + .withInput({ yehuda: { name: 'Yehuda' } }) + .withHelper('form', function(context, options) { return '
' + options.fn(context) + '
'; - } - }; - - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) + }) .withMessage('Context variable resolved') .toCompileTo('

Yehuda

'); }); it('block helper passing a complex path context', function() { - var string = '{{#form yehuda/cat}}

{{name}}

{{/form}}'; - - var input = { yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }; - var helpers = { - form: function(context, options) { + expectTemplate('{{#form yehuda/cat}}

{{name}}

{{/form}}') + .withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }) + .withHelper('form', function(context, options) { return '
' + options.fn(context) + '
'; - } - }; - - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) + }) .withMessage('Complex path variable resolved') .toCompileTo('

Harold

'); }); it('nested block helpers', function() { - var string = - '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}'; - - var input = { - yehuda: { name: 'Yehuda' } - }; - var helpers = { - link: function(options) { + expectTemplate( + '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}' + ) + .withInput({ + yehuda: { name: 'Yehuda' } + }) + .withHelper('link', function(options) { return '' + options.fn(this) + ''; - }, - form: function(context, options) { + }) + .withHelper('form', function(context, options) { return '
' + options.fn(context) + '
'; - } - }; - - expectTemplate(string) - .withInput(input) - .withHelpers(helpers) + }) .withMessage('Both blocks executed') .toCompileTo('

Yehuda

Hello
'); }); @@ -329,29 +261,25 @@ describe('helpers', function() { } } - var hash = { people: [{ name: 'Alan' }, { name: 'Yehuda' }] }; - var empty = { people: [] }; - var rootMessage = { - people: [], - message: "Nobody's here" - }; - - var messageString = '{{#list people}}Hello{{^}}{{message}}{{/list}}'; - // the meaning here may be kind of hard to catch, but list.not is always called, // so we should see the output of both expectTemplate(string) - .withInput(hash) + .withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] }) .withHelpers({ list: list }) .withMessage('an inverse wrapper is passed in as a new context') .toCompileTo(''); + expectTemplate(string) - .withInput(empty) + .withInput({ people: [] }) .withHelpers({ list: list }) .withMessage('an inverse wrapper can be optionally called') .toCompileTo("

Nobody's here

"); - expectTemplate(messageString) - .withInput(rootMessage) + + expectTemplate('{{#list people}}Hello{{^}}{{message}}{{/list}}') + .withInput({ + people: [], + message: "Nobody's here" + }) .withHelpers({ list: list }) .withMessage('the context of an inverse is the parent of the block') .toCompileTo('

Nobody's here

'); @@ -369,10 +297,12 @@ describe('helpers', function() { return 'fail'; } }; + expectTemplate('{{./helper 1}}') .withInput(hash) .withHelpers(helpers) .toCompileTo('winning'); + expectTemplate('{{hash/helper 1}}') .withInput(hash) .withHelpers(helpers) @@ -412,6 +342,7 @@ describe('helpers', function() { }) .withMessage('helpers hash has precedence escaped expansion') .toCompileTo('helpers'); + expectTemplate('{{lookup}}') .withInput({ lookup: 'Explicit' }) .withHelpers({ @@ -484,6 +415,7 @@ describe('helpers', function() { .withInput({ cruel: 'cruel' }) .toCompileTo('found it! Goodbye cruel world!!'); }); + it('fails with multiple and args', function() { shouldThrow( function() { @@ -506,9 +438,8 @@ describe('helpers', function() { }); it('decimal number literals work', function() { - var string = 'Message: {{hello -1.2 1.2}}'; - var helpers = { - hello: function(times, times2) { + expectTemplate('Message: {{hello -1.2 1.2}}') + .withHelper('hello', function(times, times2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -516,35 +447,27 @@ describe('helpers', function() { times2 = 'NaN'; } return 'Hello ' + times + ' ' + times2 + ' times'; - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('template with a negative integer literal') .toCompileTo('Message: Hello -1.2 1.2 times'); }); it('negative number literals work', function() { - var string = 'Message: {{hello -12}}'; - var helpers = { - hello: function(times) { + expectTemplate('Message: {{hello -12}}') + .withHelper('hello', function(times) { if (typeof times !== 'number') { times = 'NaN'; } return 'Hello ' + times + ' times'; - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('template with a negative integer literal') .toCompileTo('Message: Hello -12 times'); }); describe('String literal parameters', function() { it('simple literals work', function() { - var string = 'Message: {{hello "world" 12 true false}}'; - var helpers = { - hello: function(param, times, bool1, bool2) { + expectTemplate('Message: {{hello "world" 12 true false}}') + .withHelper('hello', function(param, times, bool1, bool2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -557,90 +480,65 @@ describe('helpers', function() { return ( 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 ); - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('template with a simple String literal') .toCompileTo('Message: Hello world 12 times: true false'); }); it('using a quote in the middle of a parameter raises an error', function() { - var string = 'Message: {{hello wo"rld"}}'; - expectTemplate(string).toThrow(Error); + expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error); }); it('escaping a String is possible', function() { - var string = 'Message: {{{hello "\\"world\\""}}}'; - var helpers = { - hello: function(param) { + expectTemplate('Message: {{{hello "\\"world\\""}}}') + .withHelper('hello', function(param) { return 'Hello ' + param; - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('template with an escaped String literal') .toCompileTo('Message: Hello "world"'); }); it("it works with ' marks", function() { - var string = 'Message: {{{hello "Alan\'s world"}}}'; - var helpers = { - hello: function(param) { + expectTemplate('Message: {{{hello "Alan\'s world"}}}') + .withHelper('hello', function(param) { return 'Hello ' + param; - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage("template with a ' mark") .toCompileTo("Message: Hello Alan's world"); }); }); it('negative number literals work', function() { - var string = 'Message: {{hello -12}}'; - var helpers = { - hello: function(times) { + expectTemplate('Message: {{hello -12}}') + .withHelper('hello', function(times) { if (typeof times !== 'number') { times = 'NaN'; } return 'Hello ' + times + ' times'; - } - }; - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('template with a negative integer literal') .toCompileTo('Message: Hello -12 times'); }); describe('multiple parameters', function() { it('simple multi-params work', function() { - var string = 'Message: {{goodbye cruel world}}'; - var hash = { cruel: 'cruel', world: 'world' }; - var helpers = { - goodbye: function(cruel, world) { + expectTemplate('Message: {{goodbye cruel world}}') + .withInput({ cruel: 'cruel', world: 'world' }) + .withHelper('goodbye', function(cruel, world) { return 'Goodbye ' + cruel + ' ' + world; - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .withMessage('regular helpers with multiple params') .toCompileTo('Message: Goodbye cruel world'); }); it('block multi-params work', function() { - var string = - 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'; - var hash = { cruel: 'cruel', world: 'world' }; - var helpers = { - goodbye: function(cruel, world, options) { + expectTemplate( + 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}' + ) + .withInput({ cruel: 'cruel', world: 'world' }) + .withHelper('goodbye', function(cruel, world, options) { return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world }); - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) + }) .withMessage('block helpers with multiple params') .toCompileTo('Message: Goodbye cruel world'); }); @@ -648,10 +546,8 @@ describe('helpers', function() { describe('hash', function() { it('helpers can take an optional hash', function() { - var string = '{{goodbye cruel="CRUEL" world="WORLD" times=12}}'; - - var helpers = { - goodbye: function(options) { + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -661,51 +557,36 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var context = {}; - - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + }) .withMessage('Helper output hash') .toCompileTo('GOODBYE CRUEL WORLD 12 TIMES'); }); it('helpers can take an optional hash with booleans', function() { - var helpers = { - goodbye: function(options) { - if (options.hash.print === true) { - return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world; - } else if (options.hash.print === false) { - return 'NOT PRINTING'; - } else { - return 'THIS SHOULD NOT HAPPEN'; - } + function goodbye(options) { + if (options.hash.print === true) { + return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world; + } else if (options.hash.print === false) { + return 'NOT PRINTING'; + } else { + return 'THIS SHOULD NOT HAPPEN'; } - }; - - var context = {}; + } expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}') - .withHelpers(helpers) - .withInput(context) + .withHelper('goodbye', goodbye) .withMessage('Helper output hash') .toCompileTo('GOODBYE CRUEL WORLD'); expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=false}}') - .withHelpers(helpers) - .withInput(context) + .withHelper('goodbye', goodbye) .withMessage('Boolean helper parameter honored') .toCompileTo('NOT PRINTING'); }); it('block helpers can take an optional hash', function() { - var string = '{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'; - - var helpers = { - goodbye: function(options) { + expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -715,20 +596,14 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('Hash parameters output') .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); it('block helpers can take an optional hash with single quoted stings', function() { - var string = '{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'; - - var helpers = { - goodbye: function(options) { + expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -738,35 +613,29 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - expectTemplate(string) - .withHelpers(helpers) + }) .withMessage('Hash parameters output') .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); it('block helpers can take an optional hash with booleans', function() { - var helpers = { - goodbye: function(options) { - if (options.hash.print === true) { - return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this); - } else if (options.hash.print === false) { - return 'NOT PRINTING'; - } else { - return 'THIS SHOULD NOT HAPPEN'; - } + function goodbye(options) { + if (options.hash.print === true) { + return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this); + } else if (options.hash.print === false) { + return 'NOT PRINTING'; + } else { + return 'THIS SHOULD NOT HAPPEN'; } - }; + } expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}') - .withHelpers(helpers) + .withHelper('goodbye', goodbye) .withMessage('Boolean hash parameter honored') .toCompileTo('GOODBYE CRUEL world'); expectTemplate('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}') - .withHelpers(helpers) + .withHelper('goodbye', goodbye) .withMessage('Boolean hash parameter honored') .toCompileTo('NOT PRINTING'); }); @@ -780,144 +649,104 @@ describe('helpers', function() { }); it('if a context is not found, custom helperMissing is used', function() { - var string = '{{hello}} {{link_to world}}'; - var context = { hello: 'Hello', world: 'world' }; - - var helpers = { - helperMissing: function(mesg, options) { + expectTemplate('{{hello}} {{link_to world}}') + .withInput({ hello: 'Hello', world: 'world' }) + .withHelper('helperMissing', function(mesg, options) { if (options.name === 'link_to') { return new Handlebars.SafeString('' + mesg + ''); } - } - }; - - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + }) .toCompileTo('Hello world'); }); it('if a value is not found, custom helperMissing is used', function() { - var string = '{{hello}} {{link_to}}'; - var context = { hello: 'Hello', world: 'world' }; - - var helpers = { - helperMissing: function(options) { + expectTemplate('{{hello}} {{link_to}}') + .withInput({ hello: 'Hello', world: 'world' }) + .withHelper('helperMissing', function(options) { if (options.name === 'link_to') { return new Handlebars.SafeString('winning'); } - } - }; - - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + }) .toCompileTo('Hello winning'); }); }); describe('knownHelpers', function() { it('Known helper should render helper', function() { - var string = '{{hello}}'; - var compileOptions = { - knownHelpers: { hello: true } - }; - - var helpers = { - hello: function() { + expectTemplate('{{hello}}') + .withCompileOptions({ + knownHelpers: { hello: true } + }) + .withHelper('hello', function() { return 'foo'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + }) .toCompileTo('foo'); }); it('Unknown helper in knownHelpers only mode should be passed as undefined', function() { - var string = '{{typeof hello}}'; - var compileOptions = { - knownHelpers: { typeof: true }, - knownHelpersOnly: true - }; - - var helpers = { - typeof: function(arg) { + expectTemplate('{{typeof hello}}') + .withCompileOptions({ + knownHelpers: { typeof: true }, + knownHelpersOnly: true + }) + .withHelper('typeof', function(arg) { return typeof arg; - }, - hello: function() { + }) + .withHelper('hello', function() { return 'foo'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + }) .toCompileTo('undefined'); }); - it('Builtin helpers available in knownHelpers only mode', function() { - var string = '{{#unless foo}}bar{{/unless}}'; - var compileOptions = { - knownHelpersOnly: true - }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('Builtin helpers available in knownHelpers only mode', function() { + expectTemplate('{{#unless foo}}bar{{/unless}}') + .withCompileOptions({ + knownHelpersOnly: true + }) .toCompileTo('bar'); }); + it('Field lookup works in knownHelpers only mode', function() { - var string = '{{foo}}'; - var compileOptions = { - knownHelpersOnly: true - }; - - var input = { foo: 'bar' }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withInput(input) + expectTemplate('{{foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'bar' }) .toCompileTo('bar'); }); + it('Conditional blocks work in knownHelpers only mode', function() { - var string = '{{#foo}}bar{{/foo}}'; - var compileOptions = { - knownHelpersOnly: true - }; - - var input = { foo: 'baz' }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withInput(input) + expectTemplate('{{#foo}}bar{{/foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'baz' }) .toCompileTo('bar'); }); + it('Invert blocks work in knownHelpers only mode', function() { - var string = '{{^foo}}bar{{/foo}}'; - var compileOptions = { - knownHelpersOnly: true - }; - - var input = { foo: false }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withInput(input) + expectTemplate('{{^foo}}bar{{/foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: false }) .toCompileTo('bar'); }); + it('Functions are bound to the context in knownHelpers only mode', function() { - var string = '{{foo}}'; - var compileOptions = { - knownHelpersOnly: true - }; - var input = { - foo: function() { - return this.bar; - }, - bar: 'bar' - }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withInput(input) + expectTemplate('{{foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ + foo: function() { + return this.bar; + }, + bar: 'bar' + }) .toCompileTo('bar'); }); + it('Unknown helper call in knownHelpers only mode should throw', function() { expectTemplate('{{typeof hello}}') .withCompileOptions({ knownHelpersOnly: true }) @@ -927,34 +756,30 @@ describe('helpers', function() { describe('blockHelperMissing', function() { it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() { - var string = '{{#truthy}}yep{{/truthy}}'; - var data = { - truthy: function() { - return true; - } - }; - expectTemplate(string) - .withInput(data) + expectTemplate('{{#truthy}}yep{{/truthy}}') + .withInput({ + truthy: function() { + return true; + } + }) .toCompileTo('yep'); }); + it('lambdas resolved by blockHelperMissing are bound to the context', function() { - var string = '{{#truthy}}yep{{/truthy}}'; - var boundData = { - truthy: function() { - return this.truthiness(); - }, - truthiness: function() { - return false; - } - }; - expectTemplate(string) - .withInput(boundData) + expectTemplate('{{#truthy}}yep{{/truthy}}') + .withInput({ + truthy: function() { + return this.truthiness(); + }, + truthiness: function() { + return false; + } + }) .toCompileTo(''); }); }); describe('name field', function() { - var context = {}; var helpers = { blockHelperMissing: function() { return 'missing: ' + arguments[arguments.length - 1].name; @@ -969,43 +794,40 @@ describe('helpers', function() { it('should include in ambiguous mustache calls', function() { expectTemplate('{{helper}}') - .withInput(context) .withHelpers(helpers) .toCompileTo('ran: helper'); }); + it('should include in helper mustache calls', function() { expectTemplate('{{helper 1}}') - .withInput(context) .withHelpers(helpers) .toCompileTo('ran: helper'); }); + it('should include in ambiguous block calls', function() { expectTemplate('{{#helper}}{{/helper}}') - .withInput(context) .withHelpers(helpers) .toCompileTo('ran: helper'); }); + it('should include in simple block calls', function() { expectTemplate('{{#./helper}}{{/./helper}}') - .withInput(context) .withHelpers(helpers) .toCompileTo('missing: ./helper'); }); + it('should include in helper block calls', function() { expectTemplate('{{#helper 1}}{{/helper}}') - .withInput(context) .withHelpers(helpers) .toCompileTo('ran: helper'); }); + it('should include in known helper calls', function() { - var string = '{{helper}}'; - var compileOptions = { - knownHelpers: { helper: true }, - knownHelpersOnly: true - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate('{{helper}}') + .withCompileOptions({ + knownHelpers: { helper: true }, + knownHelpersOnly: true + }) .withHelpers(helpers) .toCompileTo('ran: helper'); }); @@ -1027,101 +849,67 @@ describe('helpers', function() { describe('name conflicts', function() { it('helpers take precedence over same-named context properties', function() { - var string = '{{goodbye}} {{cruel world}}'; - - var helpers = { - goodbye: function() { + expectTemplate('{{goodbye}} {{cruel world}}') + .withHelper('goodbye', function() { return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - expectTemplate(string) - .withHelpers(helpers) - .withInput(context) + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD'); }); it('helpers take precedence over same-named context properties$', function() { - var string = '{{#goodbye}} {{cruel world}}{{/goodbye}}'; - - var helpers = { - goodbye: function(options) { + expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}') + .withHelper('goodbye', function(options) { return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - expectTemplate(string) - .withHelpers(helpers) - .withInput(context) + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD'); }); it('Scoped names take precedence over helpers', function() { - var string = '{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}'; - - var helpers = { - goodbye: function() { + expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}') + .withHelper('goodbye', function() { return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - expectTemplate(string) - .withHelpers(helpers) - .withInput(context) + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) .withMessage('Helper not executed') .toCompileTo('goodbye cruel WORLD cruel GOODBYE'); }); it('Scoped names take precedence over block helpers', function() { - var string = '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}'; - - var helpers = { - goodbye: function(options) { + expectTemplate( + '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' + ) + .withHelper('goodbye', function(options) { return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - expectTemplate(string) - .withHelpers(helpers) - .withInput(context) + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD goodbye'); }); @@ -1129,57 +917,49 @@ describe('helpers', function() { describe('block params', function() { it('should take presedence over context values', function() { - var hash = { value: 'foo' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') + .withInput({ value: 'foo' }) + .withHelper('goodbyes', function(options) { equals(options.fn.blockParams, 1); return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); - } - }; - expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('1foo'); }); + it('should take presedence over helper values', function() { - var hash = {}; - var helpers = { - value: function() { + expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') + .withHelper('value', function() { return 'foo'; - }, - goodbyes: function(options) { + }) + .withHelper('goodbyes', function(options) { equals(options.fn.blockParams, 1); return options.fn({}, { blockParams: [1, 2] }); - } - }; - expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('1foo'); }); + it('should not take presedence over pathed values', function() { - var hash = { value: 'bar' }; - var helpers = { - value: function() { - return 'foo'; - }, - goodbyes: function(options) { - equals(options.fn.blockParams, 1); - return options.fn(this, { blockParams: [1, 2] }); - } - }; expectTemplate( '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' ) - .withInput(hash) - .withHelpers(helpers) + .withInput({ value: 'bar' }) + .withHelper('value', function() { + return 'foo'; + }) + .withHelper('goodbyes', function(options) { + equals(options.fn.blockParams, 1); + return options.fn(this, { blockParams: [1, 2] }); + }) .toCompileTo('barfoo'); }); + it('should take presednece over parent block params', function() { - var hash = { value: 'foo' }, - value = 1; - var helpers = { - goodbyes: function(options) { + var value = 1; + expectTemplate( + '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' + ) + .withInput({ value: 'foo' }) + .withHelper('goodbyes', function(options) { return options.fn( { value: 'bar' }, { @@ -1187,72 +967,70 @@ describe('helpers', function() { options.fn.blockParams === 1 ? [value++, value++] : undefined } ); - } - }; - expectTemplate( - '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' - ) - .withInput(hash) - .withHelpers(helpers) + }) .toCompileTo('13foo'); }); it('should allow block params on chained helpers', function() { - var hash = { value: 'foo' }; - var helpers = { - goodbyes: function(options) { - equals(options.fn.blockParams, 1); - return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); - } - }; expectTemplate( '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' ) - .withInput(hash) - .withHelpers(helpers) + .withInput({ value: 'foo' }) + .withHelper('goodbyes', function(options) { + equals(options.fn.blockParams, 1); + return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); + }) .toCompileTo('1foo'); }); }); describe('built-in helpers malformed arguments ', function() { it('if helper - too few arguments', function() { - var string = '{{#if}}{{/if}}'; - expectTemplate(string).toThrow(/#if requires exactly one argument/); + expectTemplate('{{#if}}{{/if}}').toThrow( + /#if requires exactly one argument/ + ); }); it('if helper - too many arguments, string', function() { - var string = '{{#if test "string"}}{{/if}}'; - expectTemplate(string).toThrow(/#if requires exactly one argument/); + expectTemplate('{{#if test "string"}}{{/if}}').toThrow( + /#if requires exactly one argument/ + ); }); it('if helper - too many arguments, undefined', function() { - var string = '{{#if test undefined}}{{/if}}'; - expectTemplate(string).toThrow(/#if requires exactly one argument/); + expectTemplate('{{#if test undefined}}{{/if}}').toThrow( + /#if requires exactly one argument/ + ); }); it('if helper - too many arguments, null', function() { - var string = '{{#if test null}}{{/if}}'; - expectTemplate(string).toThrow(/#if requires exactly one argument/); + expectTemplate('{{#if test null}}{{/if}}').toThrow( + /#if requires exactly one argument/ + ); }); it('unless helper - too few arguments', function() { - var string = '{{#unless}}{{/unless}}'; - expectTemplate(string).toThrow(/#unless requires exactly one argument/); + expectTemplate('{{#unless}}{{/unless}}').toThrow( + /#unless requires exactly one argument/ + ); }); it('unless helper - too many arguments', function() { - var string = '{{#unless test null}}{{/unless}}'; - expectTemplate(string).toThrow(/#unless requires exactly one argument/); + expectTemplate('{{#unless test null}}{{/unless}}').toThrow( + /#unless requires exactly one argument/ + ); }); it('with helper - too few arguments', function() { - var string = '{{#with}}{{/with}}'; - expectTemplate(string).toThrow(/#with requires exactly one argument/); + expectTemplate('{{#with}}{{/with}}').toThrow( + /#with requires exactly one argument/ + ); }); it('with helper - too many arguments', function() { - var string = '{{#with test "string"}}{{/with}}'; - expectTemplate(string).toThrow(/#with requires exactly one argument/); + expectTemplate('{{#with test "string"}}{{/with}}').toThrow( + /#with requires exactly one argument/ + ); }); }); diff --git a/spec/partials.js b/spec/partials.js index a98bfd23f..df092267d 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -8,10 +8,12 @@ describe('partials', function() { { name: 'Alan', url: 'http://alan' } ] }; + expectTemplate(string) .withInput(hash) .withPartials({ dude: partial }) .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + expectTemplate(string) .withInput(hash) .withPartials({ dude: partial }) @@ -34,11 +36,13 @@ describe('partials', function() { return 'dude'; } }; + expectTemplate(string) .withInput(hash) .withHelpers(helpers) .withPartials({ dude: partial }) .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + expectTemplate(string) .withInput(hash) .withHelpers(helpers) @@ -47,39 +51,31 @@ describe('partials', function() { .withCompileOptions({ data: false }) .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); + it('failing dynamic partials', function() { - var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) '; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - var helpers = { - partial: function() { + expectTemplate('Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withHelper('partial', function() { return 'missing'; - } - }; - expectTemplate(string) - .withInput(hash) - .withHelpers(helpers) - .withPartial('dude', partial) + }) + .withPartial('dude', '{{name}} ({{url}}) ') .toThrow(Handlebars.Exception, 'The partial missing could not be found'); }); it('partials with context', function() { - var string = 'Dudes: {{>dude dudes}}'; - var partial = '{{#this}}{{name}} ({{url}}) {{/this}}'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{>dude dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{#this}}{{name}} ({{url}}) {{/this}}') .withMessage('Partials can be passed a context') .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); @@ -92,35 +88,29 @@ describe('partials', function() { { name: 'Alan', url: 'http://alan' } ] }; + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') .withInput(hash) - .withPartials({ dude: partial }) + .withPartial('dude', partial) .withCompileOptions({ explicitPartialContext: true }) .toCompileTo('Dudes: () () '); + expectTemplate('Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}') .withInput(hash) - .withPartials({ dude: partial }) + .withPartial('dude', partial) .withCompileOptions({ explicitPartialContext: true }) .toCompileTo('Dudes: foo () foo () '); }); it('partials with string context', function() { - var string = 'Dudes: {{>dude "dudes"}}'; - var partial = '{{.}}'; - var hash = {}; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{>dude "dudes"}}') + .withPartial('dude', '{{.}}') .toCompileTo('Dudes: dudes'); }); it('partials with undefined context', function() { - var string = 'Dudes: {{>dude dudes}}'; - var partial = '{{foo}} Empty'; - var hash = {}; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{>dude dudes}}') + .withPartial('dude', '{{foo}} Empty') .toCompileTo('Dudes: Empty'); }); @@ -132,37 +122,30 @@ describe('partials', function() { }); it('partials with parameters', function() { - var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}'; - var partial = '{{others.foo}}{{name}} ({{url}}) '; - var hash = { - foo: 'bar', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}') + .withInput({ + foo: 'bar', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{others.foo}}{{name}} ({{url}}) ') .withMessage('Basic partials output based on current context.') .toCompileTo('Dudes: barYehuda (http://yehuda) barAlan (http://alan) '); }); it('partial in a partial', function() { - var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}'; - var dude = '{{name}} {{> url}} '; - var url = '{{url}}'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) .withPartials({ - dude: dude, - url: url + dude: '{{name}} {{> url}} ', + url: '{{url}}' }) .withMessage('Partials are rendered inside of other partials') .toCompileTo( @@ -196,52 +179,41 @@ describe('partials', function() { }); it('rendering function partial in vm mode', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; function partial(context) { return context.name + ' (' + context.url + ') '; } - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', partial) .withMessage('Function partials output based in VM.') .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); it('GH-14: a partial preceding a selector', function() { - var string = 'Dudes: {{>dude}} {{anotherDude}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: dude }) + expectTemplate('Dudes: {{>dude}} {{anotherDude}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('dude', '{{name}}') .withMessage('Regular selectors can follow a partial') .toCompileTo('Dudes: Jeepers Creepers'); }); it('Partials with slash paths', function() { - var string = 'Dudes: {{> shared/dude}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ 'shared/dude': dude }) + expectTemplate('Dudes: {{> shared/dude}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude', '{{name}}') .withMessage('Partials can use literal paths') .toCompileTo('Dudes: Jeepers'); }); it('Partials with slash and point paths', function() { - var string = 'Dudes: {{> shared/dude.thing}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ 'shared/dude.thing': dude }) + expectTemplate('Dudes: {{> shared/dude.thing}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude.thing', '{{name}}') .withMessage('Partials can use literal with points in paths') .toCompileTo('Dudes: Jeepers'); }); @@ -249,12 +221,9 @@ describe('partials', function() { it('Global Partials', function() { handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}'); - var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ 'shared/dude': dude }) + expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude', '{{name}}') .withMessage('Partials can use globals or passed') .toCompileTo('Dudes: Jeepers Creepers'); @@ -268,71 +237,54 @@ describe('partials', function() { globalTest: '{{anotherDude}}' }); - var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) + expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) .withPartial('notused', 'notused') // trick the test bench into running with partials enabled .withMessage('Partials can use globals or passed') .toCompileTo('Dudes: Jeepers Creepers'); }); it('Partials with integer path', function() { - var string = 'Dudes: {{> 404}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ 404: dude }) + expectTemplate('Dudes: {{> 404}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial(404, '{{name}}') .withMessage('Partials can use literal paths') .toCompileTo('Dudes: Jeepers'); }); it('Partials with complex path', function() { - var string = 'Dudes: {{> 404/asdf?.bar}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ '404/asdf?.bar': dude }) + expectTemplate('Dudes: {{> 404/asdf?.bar}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('404/asdf?.bar', '{{name}}') .withMessage('Partials can use literal paths') .toCompileTo('Dudes: Jeepers'); }); it('Partials with escaped', function() { - var string = 'Dudes: {{> [+404/asdf?.bar]}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ '+404/asdf?.bar': dude }) + expectTemplate('Dudes: {{> [+404/asdf?.bar]}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('+404/asdf?.bar', '{{name}}') .withMessage('Partials can use literal paths') .toCompileTo('Dudes: Jeepers'); }); it('Partials with string', function() { - var string = "Dudes: {{> '+404/asdf?.bar'}}"; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - expectTemplate(string) - .withInput(hash) - .withPartials({ '+404/asdf?.bar': dude }) + expectTemplate("Dudes: {{> '+404/asdf?.bar'}}") + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('+404/asdf?.bar', '{{name}}') .withMessage('Partials can use literal paths') .toCompileTo('Dudes: Jeepers'); }); it('should handle empty partial', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; - var partial = ''; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '') .toCompileTo('Dudes: '); }); @@ -352,11 +304,13 @@ describe('partials', function() { it('should render partial block as default', function() { expectTemplate('{{#> dude}}success{{/dude}}').toCompileTo('success'); }); + it('should execute default block with proper context', function() { expectTemplate('{{#> dude context}}{{value}}{{/dude}}') .withInput({ context: { value: 'success' } }) .toCompileTo('success'); }); + it('should propagate block parameters to default block', function() { expectTemplate( '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' @@ -376,11 +330,13 @@ describe('partials', function() { .withPartials({ dude: '{{> @partial-block }}' }) .toCompileTo('success'); }); + it('should be able to render the partial-block twice', function() { expectTemplate('{{#> dude}}success{{/dude}}') .withPartials({ dude: '{{> @partial-block }} {{> @partial-block }}' }) .toCompileTo('success success'); }); + it('should render block from partial with context', function() { expectTemplate('{{#> dude}}{{value}}{{/dude}}') .withInput({ context: { value: 'success' } }) @@ -415,6 +371,7 @@ describe('partials', function() { '' ); }); + it('should render block from partial with context (twice)', function() { expectTemplate('{{#> dude}}{{value}}{{/dude}}') .withInput({ context: { value: 'success' } }) @@ -424,6 +381,7 @@ describe('partials', function() { }) .toCompileTo('success success'); }); + it('should render block from partial with context', function() { expectTemplate('{{#> dude}}{{../context/value}}{{/dude}}') .withInput({ context: { value: 'success' } }) @@ -432,6 +390,7 @@ describe('partials', function() { }) .toCompileTo('success'); }); + it('should render block from partial with block params', function() { expectTemplate( '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' @@ -440,6 +399,7 @@ describe('partials', function() { .withPartials({ dude: '{{> @partial-block }}' }) .toCompileTo('success'); }); + it('should render nested partial blocks', function() { expectTemplate('') .withInput({ value: 'success' }) @@ -452,6 +412,7 @@ describe('partials', function() { '' ); }); + it('should render nested partial blocks at different nesting levels', function() { expectTemplate('') .withInput({ value: 'success' }) @@ -464,6 +425,7 @@ describe('partials', function() { '' ); }); + it('should render nested partial blocks at different nesting levels (twice)', function() { expectTemplate('') .withInput({ value: 'success' }) @@ -476,6 +438,7 @@ describe('partials', function() { '' ); }); + it('should render nested partial blocks (twice at each level)', function() { expectTemplate('') .withInput({ value: 'success' }) @@ -498,19 +461,23 @@ describe('partials', function() { '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' ).toCompileTo('success'); }); + it('should overwrite multiple partials in the same template', function() { expectTemplate( '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' ).toCompileTo('success'); }); + it('should define inline partials for block', function() { expectTemplate( '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' ).toCompileTo('success'); + expectTemplate( '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}' ).toThrow(Error, /myPartial could not/); }); + it('should override global partials', function() { expectTemplate( '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' @@ -522,11 +489,13 @@ describe('partials', function() { }) .toCompileTo('success'); }); + it('should override template partials', function() { expectTemplate( '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' ).toCompileTo('success'); }); + it('should override partials down the entire stack', function() { expectTemplate( '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}' @@ -538,6 +507,7 @@ describe('partials', function() { .withPartials({ dude: '{{> myPartial }}' }) .toCompileTo('success'); }); + it('should define inline partials in partial block call', function() { expectTemplate( '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}' @@ -545,6 +515,7 @@ describe('partials', function() { .withPartials({ dude: '{{> myPartial }}' }) .toCompileTo('success'); }); + it('should render nested inline partials', function() { expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{/inline}}' + @@ -554,6 +525,7 @@ describe('partials', function() { .withInput({ value: 'success' }) .toCompileTo('success'); }); + it('should render nested inline partials with partial-blocks on different nesting levels', function() { expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{>@partial-block}}{{/inline}}' + @@ -565,6 +537,7 @@ describe('partials', function() { 'successsuccess' ); }); + it('should render nested inline partials (twice at each level)', function() { expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}} {{>@partial-block}}{{/inner}}{{/inline}}' + @@ -589,54 +562,45 @@ describe('partials', function() { describe('standalone partials', function() { it('indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: dude }) + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{name}}\n') .toCompileTo('Dudes:\n Yehuda\n Alan\n'); }); + it('nested indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n {{> url}}'; - var url = '{{url}}!\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) .withPartials({ - dude: dude, - url: url + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' }) .toCompileTo( 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n' ); }); + it('prevent nested indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n {{> url}}'; - var url = '{{url}}!\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) .withPartials({ - dude: dude, - url: url + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' }) .withCompileOptions({ preventIndent: true }) .toCompileTo( @@ -647,73 +611,66 @@ describe('partials', function() { describe('compat mode', function() { it('partials can access parents', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) .withCompileOptions({ compat: true }) .toCompileTo( 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' ); }); + it('partials can access parents with custom context', function() { - var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) .withCompileOptions({ compat: true }) .toCompileTo( 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' ); }); + it('partials can access parents without data', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) .withRuntimeOptions({ data: false }) .withCompileOptions({ data: false, compat: true }) .toCompileTo( 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' ); }); + it('partials inherit compat', function() { - var string = 'Dudes: {{> dude}}'; - var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}'; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - expectTemplate(string) - .withInput(hash) - .withPartials({ dude: partial }) + expectTemplate('Dudes: {{> dude}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}' + }) .withCompileOptions({ compat: true }) .toCompileTo( 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' diff --git a/spec/regressions.js b/spec/regressions.js index 6da9672d1..86d4634d2 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -1,21 +1,19 @@ describe('Regressions', function() { it('GH-94: Cannot read property of undefined', function() { - var data = { - books: [ - { - title: 'The origin of species', - author: { - name: 'Charles Darwin' + expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}') + .withInput({ + books: [ + { + title: 'The origin of species', + author: { + name: 'Charles Darwin' + } + }, + { + title: 'Lazarillo de Tormes' } - }, - { - title: 'Lazarillo de Tormes' - } - ] - }; - var string = '{{#books}}{{title}}{{author.name}}{{/books}}'; - expectTemplate(string) - .withInput(data) + ] + }) .withMessage('Renders without an undefined property error') .toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes'); }); @@ -28,14 +26,17 @@ describe('Regressions', function() { "inverted sections run when property isn't present in context" ) .toCompileTo('not set :: '); + expectTemplate(string) .withInput({ set: undefined }) .withMessage('inverted sections run when property is undefined') .toCompileTo('not set :: '); + expectTemplate(string) .withInput({ set: false }) .withMessage('inverted sections run when property is false') .toCompileTo('not set :: '); + expectTemplate(string) .withInput({ set: true }) .withMessage("inverted sections don't run when property is true") @@ -43,11 +44,8 @@ describe('Regressions', function() { }); it('GH-158: Using array index twice, breaks the template', function() { - var string = '{{arr.[0]}}, {{arr.[1]}}'; - var data = { arr: [1, 2] }; - - expectTemplate(string) - .withInput(data) + expectTemplate('{{arr.[0]}}, {{arr.[1]}}') + .withInput({ arr: [1, 2] }) .withMessage('it works as expected') .toCompileTo('1, 2'); }); @@ -67,6 +65,7 @@ describe('Regressions', function() { '\n' + 'Nothing to check out...\n' + '{{/hasThings}}'; + var data = { thing: function() { return 'blah'; @@ -89,21 +88,20 @@ describe('Regressions', function() { '
  • @dhg
  • \n' + '
  • @sayrer
  • \n' + '.\n'; + expectTemplate(string) .withInput(data) .toCompileTo(output); }); it('GH-408: Multiple loops fail', function() { - var context = [ - { name: 'John Doe', location: { city: 'Chicago' } }, - { name: 'Jane Doe', location: { city: 'New York' } } - ]; - - var string = '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'; - - expectTemplate(string) - .withInput(context) + expectTemplate( + '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}' + ) + .withInput([ + { name: 'John Doe', location: { city: 'Chicago' } }, + { name: 'Jane Doe', location: { city: 'New York' } } + ]) .withMessage('It should output multiple times') .toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe'); }); @@ -126,6 +124,7 @@ describe('Regressions', function() { expectTemplate(succeedingTemplate) .withHelpers(helpers) .toCompileTo(' Expected '); + expectTemplate(failingTemplate) .withHelpers(helpers) .toCompileTo(' Expected '); @@ -159,27 +158,24 @@ describe('Regressions', function() { }); it('GH-676: Using array in escaping mustache fails', function() { - var string = '{{arr}}'; var data = { arr: [1, 2] }; - expectTemplate(string) + expectTemplate('{{arr}}') .withInput(data) .withMessage('it works as expected') .toCompileTo(data.arr.toString()); }); it('Mustache man page', function() { - var string = - 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}'; - var data = { - name: 'Chris', - value: 10000, - taxed_value: 10000 - 10000 * 0.4, - in_ca: true - }; - - expectTemplate(string) - .withInput(data) + expectTemplate( + 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}' + ) + .withInput({ + name: 'Chris', + value: 10000, + taxed_value: 10000 - 10000 * 0.4, + in_ca: true + }) .withMessage('the hello world mustache example works') .toCompileTo( 'Hello Chris. You have just won $10000! Well, $6000, after taxes.' @@ -202,58 +198,49 @@ describe('Regressions', function() { }); it('GH-837: undefined values for helpers', function() { - var helpers = { - str: function(value) { - return value + ''; - } - }; - expectTemplate('{{str bar.baz}}') - .withHelpers(helpers) + .withHelpers({ + str: function(value) { + return value + ''; + } + }) .toCompileTo('undefined'); }); it('GH-926: Depths and de-dupe', function() { - var context = { - name: 'foo', - data: [1], - notData: [1] - }; - - var string = - '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'; - - expectTemplate(string) - .withInput(context) + expectTemplate( + '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}' + ) + .withInput({ + name: 'foo', + data: [1], + notData: [1] + }) .toCompileTo('foo'); }); it('GH-1021: Each empty string key', function() { - var data = { - '': 'foo', - name: 'Chris', - value: 10000 - }; - expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}') - .withInput({ data: data }) + .withInput({ + data: { + '': 'foo', + name: 'Chris', + value: 10000 + } + }) .toCompileTo('Key: \nKey: name\nKey: value\n'); }); it('GH-1054: Should handle simple safe string responses', function() { - var root = '{{#wrap}}{{>partial}}{{/wrap}}'; - var partials = { - partial: '{{#wrap}}{{/wrap}}' - }; - var helpers = { - wrap: function(options) { - return new Handlebars.SafeString(options.fn()); - } - }; - - expectTemplate(root) - .withHelpers(helpers) - .withPartials(partials) + expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') + .withHelpers({ + wrap: function(options) { + return new Handlebars.SafeString(options.fn()); + } + }) + .withPartials({ + partial: '{{#wrap}}{{/wrap}}' + }) .toCompileTo(''); }); @@ -267,91 +254,81 @@ describe('Regressions', function() { }); it('GH-1093: Undefined helper context', function() { - var obj = { foo: undefined, bar: 'bat' }; - var helpers = { - helper: function() { - // It's valid to execute a block against an undefined context, but - // helpers can not do so, so we expect to have an empty object here; - for (var name in this) { - if (Object.prototype.hasOwnProperty.call(this, name)) { - return 'found'; + expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}') + .withInput({ obj: { foo: undefined, bar: 'bat' } }) + .withHelpers({ + helper: function() { + // It's valid to execute a block against an undefined context, but + // helpers can not do so, so we expect to have an empty object here; + for (var name in this) { + if (Object.prototype.hasOwnProperty.call(this, name)) { + return 'found'; + } } + // And to make IE happy, check for the known string as length is not enumerated. + return this === 'bat' ? 'found' : 'not'; } - // And to make IE happy, check for the known string as length is not enumerated. - return this === 'bat' ? 'found' : 'not'; - } - }; - - expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}') - .withInput({ obj: obj }) - .withHelpers(helpers) + }) .toCompileTo('notfoundbat'); }); it('should support multiple levels of inline partials', function() { - var string = - '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' - }; - expectTemplate(string) - .withPartials(partials) + expectTemplate( + '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}' + ) + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' + }) .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1089: should support failover content in multiple levels of inline partials', function() { - var string = '{{#> layout}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' - }; - expectTemplate(string) - .withPartials(partials) + expectTemplate('{{#> layout}}{{/layout}}') + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' + }) .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1099: should support greater than 3 nested levels of inline partials', function() { - var string = '{{#> layout}}Outer{{/layout}}'; - var partials = { - layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', - inner: '' - }; - expectTemplate(string) - .withPartials(partials) + expectTemplate('{{#> layout}}Outer{{/layout}}') + .withPartials({ + layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', + inner: '' + }) .toCompileTo('Outer'); }); it('GH-1135 : Context handling within each iteration', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - myif: function(conditional, options) { - if (conditional) { - return options.fn(this); - } else { - return options.inverse(this); - } - } - }; - expectTemplate( '{{#each array}}\n' + ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' + ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' + '{{/each}}' ) - .withInput(obj) - .withHelpers(helpers) + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + myif: function(conditional, options) { + if (conditional) { + return options.fn(this); + } else { + return options.inverse(this); + } + } + }) .toCompileTo(' 1. IF: John--\n' + ' 2. MYIF: John==\n'); }); it('GH-1186: Support block params for existing programs', function() { - var string = + expectTemplate( '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + - '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + - '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'; - - expectTemplate(string) + '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + + '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' + ) .withInput({ listOne: ['a'], listTwo: ['b'] @@ -361,9 +338,9 @@ describe('Regressions', function() { }); it('GH-1319: "unless" breaks when "each" value equals "null"', function() { - var string = - '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; - expectTemplate(string) + expectTemplate( + '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}' + ) .withInput({ value: 'parent', list: [null, 'a'] @@ -373,14 +350,12 @@ describe('Regressions', function() { }); it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { - var string = 'template {{>partial}} template'; - var partials = { - partialWithBlock: - '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', - partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' - }; - expectTemplate(string) - .withPartials(partials) + expectTemplate('template {{>partial}} template') + .withPartials({ + partialWithBlock: + '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', + partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' + }) .toCompileTo('template block partial block template'); }); @@ -480,16 +455,13 @@ describe('Regressions', function() { }); it('should allow hash with protected array names', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - helpa: function(options) { - return options.hash.length; - } - }; - expectTemplate('{{helpa length="foo"}}') - .withInput(obj) - .withHelpers(helpers) + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + helpa: function(options) { + return options.hash.length; + } + }) .toCompileTo('foo'); }); diff --git a/spec/security.js b/spec/security.js index e01dc3db7..25aabeb7b 100644 --- a/spec/security.js +++ b/spec/security.js @@ -23,6 +23,7 @@ describe('security issues', function() { expectTemplate('{{constructor.name}}') .withInput({ constructor: { name: 'here we go' } }) .toCompileTo('here we go'); + expectTemplate('{{lookup (lookup this "constructor") "name"}}') .withInput({ constructor: { name: 'here we go' } }) .toCompileTo('here we go'); @@ -44,9 +45,11 @@ describe('security issues', function() { it('should throw an exception when calling "{{helperMissing}}" ', function() { expectTemplate('{{helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; expect(function() { @@ -59,14 +62,14 @@ describe('security issues', function() { }).to.throw(Error); expect(functionCalls.length).to.equal(0); }); + it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { - var input = { - fn: function() { - return 'functionInData'; - } - }; expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}') - .withInput(input) + .withInput({ + fn: function() { + return 'functionInData'; + } + }) .toThrow(Error); }); }); @@ -76,12 +79,14 @@ describe('security issues', function() { var template = Handlebars.compile('{{helperMissing}}'); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { var template = Handlebars.compile( '{{#helperMissing}}{{/helperMissing}}' ); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); @@ -95,6 +100,7 @@ describe('security issues', function() { ); equals(functionCalls.length, 1); }); + it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { var template = Handlebars.compile( '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}' diff --git a/spec/strict.js b/spec/strict.js index 0e02afe2e..8e48fab5e 100644 --- a/spec/strict.js +++ b/spec/strict.js @@ -7,6 +7,7 @@ describe('strict', function() { .withCompileOptions({ strict: true }) .toThrow(Exception, /"hello" not defined in/); }); + it('should error on missing child', function() { expectTemplate('{{hello.bar}}') .withCompileOptions({ strict: true }) @@ -18,12 +19,14 @@ describe('strict', function() { .withInput({ hello: {} }) .toThrow(Exception, /"bar" not defined in/); }); + it('should handle explicit undefined', function() { expectTemplate('{{hello.bar}}') .withCompileOptions({ strict: true }) .withInput({ hello: { bar: undefined } }) .toCompileTo(''); }); + it('should error on missing property lookup in known helpers mode', function() { expectTemplate('{{hello}}') .withCompileOptions({ @@ -32,6 +35,7 @@ describe('strict', function() { }) .toThrow(Exception, /"hello" not defined in/); }); + it('should error on missing context', function() { expectTemplate('{{hello}}') .withCompileOptions({ strict: true }) @@ -59,6 +63,7 @@ describe('strict', function() { .withInput({ foo: true }) .toThrow(Exception, /"hello" not defined in/); }); + it('should throw on ambiguous blocks', function() { expectTemplate('{{#hello}}{{/hello}}') .withCompileOptions({ strict: true }) @@ -81,20 +86,17 @@ describe('strict', function() { }); it('should allow undefined hash when passed to helpers', function() { - var string = '{{helper value=@foo}}'; - var compileOptions = { - strict: true - }; - var helpers = { - helper: function(options) { - equals('value' in options.hash, true); - equals(options.hash.value, undefined); - return 'success'; - } - }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{helper value=@foo}}') + .withCompileOptions({ + strict: true + }) + .withHelpers({ + helper: function(options) { + equals('value' in options.hash, true); + equals(options.hash.value, undefined); + return 'success'; + } + }) .toCompileTo('success'); }); @@ -125,17 +127,20 @@ describe('strict', function() { .withCompileOptions({ assumeObjects: true }) .toCompileTo(''); }); + it('should ignore missing child', function() { expectTemplate('{{hello.bar}}') .withCompileOptions({ assumeObjects: true }) .withInput({ hello: {} }) .toCompileTo(''); }); + it('should error on missing object', function() { expectTemplate('{{hello.bar}}') .withCompileOptions({ assumeObjects: true }) .toThrow(Error); }); + it('should error on missing context', function() { expectTemplate('{{hello}}') .withCompileOptions({ assumeObjects: true }) diff --git a/spec/string-params.js b/spec/string-params.js index 0ce211ffc..c4b9a27b7 100644 --- a/spec/string-params.js +++ b/spec/string-params.js @@ -1,141 +1,117 @@ describe('string params mode', function() { it('arguments to helpers can be retrieved from options hash in string form', function() { - var string = '{{wycats is.a slave.driver}}'; - var compileOptions = { - stringParams: true - }; - - var helpers = { - wycats: function(passiveVoice, noun) { - return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun) { + return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun; + } + }) .withMessage('String parameters output') .toCompileTo('HELP ME MY BOSS is.a slave.driver'); }); it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() { - var string = '{{#wycats is.a slave.driver}}help :({{/wycats}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - wycats: function(passiveVoice, noun, options) { - return ( - 'HELP ME MY BOSS ' + - passiveVoice + - ' ' + - noun + - ': ' + - options.fn(this) - ); - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{#wycats is.a slave.driver}}help :({{/wycats}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ': ' + + options.fn(this) + ); + } + }) .withMessage('String parameters output') .toCompileTo('HELP ME MY BOSS is.a slave.driver: help :('); }); it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() { - var string = '{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - tomdale: function(desire, noun, options) { - return ( - 'STOP ME FROM READING HACKER NEWS I ' + - options.contexts[0][desire] + - ' ' + - noun - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var input = { - dale: {}, - - need: 'need-a' - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) + expectTemplate('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + ); + }, + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ + dale: {}, + + need: 'need-a' + }) .withMessage('Proper context variable output') .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke'); }); it('information about the types is passed along', function() { - var string = "{{tomdale 'need' dad.joke true false}}"; - var compileOptions = { stringParams: true }; - - var helpers = { - tomdale: function(desire, noun, trueBool, falseBool, options) { - equal(options.types[0], 'StringLiteral', 'the string type is passed'); - equal( - options.types[1], - 'PathExpression', - 'the expression type is passed' - ); - equal( - options.types[2], - 'BooleanLiteral', - 'the expression type is passed' - ); - equal(desire, 'need', 'the string form is passed for strings'); - equal(noun, 'dad.joke', 'the string form is passed for expressions'); - equal(trueBool, true, 'raw booleans are passed through'); - equal(falseBool, false, 'raw booleans are passed through'); - return 'Helper called'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate("{{tomdale 'need' dad.joke true false}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, trueBool, falseBool, options) { + equal(options.types[0], 'StringLiteral', 'the string type is passed'); + equal( + options.types[1], + 'PathExpression', + 'the expression type is passed' + ); + equal( + options.types[2], + 'BooleanLiteral', + 'the expression type is passed' + ); + equal(desire, 'need', 'the string form is passed for strings'); + equal(noun, 'dad.joke', 'the string form is passed for expressions'); + equal(trueBool, true, 'raw booleans are passed through'); + equal(falseBool, false, 'raw booleans are passed through'); + return 'Helper called'; + } + }) .toCompileTo('Helper called'); }); it('hash parameters get type information', function() { - var string = "{{tomdale he.says desire='need' noun=dad.joke bool=true}}"; - var compileOptions = { stringParams: true }; - - var helpers = { - tomdale: function(exclamation, options) { - equal(exclamation, 'he.says'); - equal(options.types[0], 'PathExpression'); - - equal(options.hashTypes.desire, 'StringLiteral'); - equal(options.hashTypes.noun, 'PathExpression'); - equal(options.hashTypes.bool, 'BooleanLiteral'); - equal(options.hash.desire, 'need'); - equal(options.hash.noun, 'dad.joke'); - equal(options.hash.bool, true); - return 'Helper called'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate("{{tomdale he.says desire='need' noun=dad.joke bool=true}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(exclamation, options) { + equal(exclamation, 'he.says'); + equal(options.types[0], 'PathExpression'); + + equal(options.hashTypes.desire, 'StringLiteral'); + equal(options.hashTypes.noun, 'PathExpression'); + equal(options.hashTypes.bool, 'BooleanLiteral'); + equal(options.hash.desire, 'need'); + equal(options.hash.noun, 'dad.joke'); + equal(options.hash.bool, true); + return 'Helper called'; + } + }) .toCompileTo('Helper called'); }); it('hash parameters get context information', function() { - var string = - "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}"; - var compileOptions = { stringParams: true }; - var context = { dale: {} }; var helpers = { @@ -155,84 +131,77 @@ describe('string params mode', function() { } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate( + "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}" + ) + .withCompileOptions({ stringParams: true }) .withHelpers(helpers) .withInput(context) .toCompileTo('Helper called'); }); it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() { - var string = - '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - tomdale: function(desire, noun, options) { - return ( - 'STOP ME FROM READING HACKER NEWS I ' + - options.contexts[0][desire] + - ' ' + - noun + - ' ' + - options.fn(this) - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var input = { - dale: {}, - - need: 'need-a' - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) + expectTemplate( + '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + + ' ' + + options.fn(this) + ); + }, + + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ + dale: {}, + + need: 'need-a' + }) .withMessage('Proper context variable output') .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke wot'); }); it('with nested block ambiguous', function() { - var string = - '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - with: function() { - return 'WITH'; - }, - view: function() { - return 'VIEW'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate( + '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + with: function() { + return 'WITH'; + }, + view: function() { + return 'VIEW'; + } + }) .toCompileTo('WITH'); }); it('should handle DATA', function() { - var string = '{{foo @bar}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - foo: function(bar, options) { - equal(bar, '@bar'); - equal(options.types[0], 'PathExpression'); - return 'Foo!'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{foo @bar}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + foo: function(bar, options) { + equal(bar, '@bar'); + equal(options.types[0], 'PathExpression'); + return 'Foo!'; + } + }) .toCompileTo('Foo!'); }); }); diff --git a/spec/subexpressions.js b/spec/subexpressions.js index 8f041816a..21b93aab9 100644 --- a/spec/subexpressions.js +++ b/spec/subexpressions.js @@ -1,72 +1,56 @@ describe('subexpressions', function() { it('arg-less helper', function() { - var string = '{{foo (bar)}}!'; - var context = {}; - var helpers = { - foo: function(val) { - return val + val; - }, - bar: function() { - return 'LOL'; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{foo (bar)}}!') + .withHelpers({ + foo: function(val) { + return val + val; + }, + bar: function() { + return 'LOL'; + } + }) .toCompileTo('LOLLOL!'); }); it('helper w args', function() { - var string = '{{blog (equal a b)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{blog (equal a b)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) .toCompileTo('val is true'); }); it('mixed paths and helpers', function() { - var string = '{{blog baz.bat (equal a b) baz.bar}}'; - - var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }; - var helpers = { - blog: function(val, that, theOther) { - return 'val is ' + val + ', ' + that + ' and ' + theOther; - }, - equal: function(x, y) { - return x === y; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{blog baz.bat (equal a b) baz.bar}}') + .withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }) + .withHelpers({ + blog: function(val, that, theOther) { + return 'val is ' + val + ', ' + that + ' and ' + theOther; + }, + equal: function(x, y) { + return x === y; + } + }) .toCompileTo('val is foo!, true and bar!'); }); it('supports much nesting', function() { - var string = '{{blog (equal (equal true true) true)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{blog (equal (equal true true) true)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) .toCompileTo('val is true'); }); @@ -85,18 +69,22 @@ describe('subexpressions', function() { .withInput(context) .withHelpers(helpers) .toCompileTo('abc-ab'); + expectTemplate('{{dash d (concat a b)}}') .withInput(context) .withHelpers(helpers) .toCompileTo('d-ab'); + expectTemplate('{{dash c.c (concat a b)}}') .withInput(context) .withHelpers(helpers) .toCompileTo('c-ab'); + expectTemplate('{{dash (concat a b) c.c}}') .withInput(context) .withHelpers(helpers) .toCompileTo('ab-c'); + expectTemplate('{{dash (concat a e.e) c.c}}') .withInput(context) .withHelpers(helpers) @@ -104,8 +92,6 @@ describe('subexpressions', function() { }); it('provides each nested helper invocation its own options hash', function() { - var string = '{{equal (equal true true) true}}'; - var lastOptions = null; var helpers = { equal: function(x, y, options) { @@ -116,208 +102,177 @@ describe('subexpressions', function() { return x === y; } }; - expectTemplate(string) + expectTemplate('{{equal (equal true true) true}}') .withHelpers(helpers) .toCompileTo('true'); }); it('with hashes', function() { - var string = "{{blog (equal (equal true true) true fun='yes')}}"; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate("{{blog (equal (equal true true) true fun='yes')}}") + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) .toCompileTo('val is true'); }); it('as hashes', function() { - var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}"; - - var helpers = { - blog: function(options) { - return 'val is ' + options.hash.fun; - }, - equal: function(x, y) { - return x === y; - } - }; - expectTemplate(string) - .withHelpers(helpers) + expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}") + .withHelpers({ + blog: function(options) { + return 'val is ' + options.hash.fun; + }, + equal: function(x, y) { + return x === y; + } + }) .toCompileTo('val is true'); }); it('multiple subexpressions in a hash', function() { - var string = - '{{input aria-label=(t "Name") placeholder=(t "Example User")}}'; - - var helpers = { - input: function(options) { - var hash = options.hash; - var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); - var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); - return new Handlebars.SafeString( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - expectTemplate(string) - .withHelpers(helpers) + expectTemplate( + '{{input aria-label=(t "Name") placeholder=(t "Example User")}}' + ) + .withHelpers({ + input: function(options) { + var hash = options.hash; + var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); + var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); + return new Handlebars.SafeString( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) .toCompileTo(''); }); it('multiple subexpressions in a hash with context', function() { - var string = - '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}'; - - var context = { - item: { - field: 'Name', - placeholder: 'Example User' - } - }; - - var helpers = { - input: function(options) { - var hash = options.hash; - var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); - var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); - return new Handlebars.SafeString( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate( + '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}' + ) + .withInput({ + item: { + field: 'Name', + placeholder: 'Example User' + } + }) + .withHelpers({ + input: function(options) { + var hash = options.hash; + var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); + var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); + return new Handlebars.SafeString( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) .toCompileTo(''); }); it('in string params mode,', function() { - var string = '{{snog (blorg foo x=y) yeah a=b}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - snog: function(a, b, options) { - equals(a, 'foo'); - equals( - options.types.length, - 2, - 'string params for outer helper processed correctly' - ); - equals( - options.types[0], - 'SubExpression', - 'string params for outer helper processed correctly' - ); - equals( - options.types[1], - 'PathExpression', - 'string params for outer helper processed correctly' - ); - return a + b; - }, + expectTemplate('{{snog (blorg foo x=y) yeah a=b}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + snog: function(a, b, options) { + equals(a, 'foo'); + equals( + options.types.length, + 2, + 'string params for outer helper processed correctly' + ); + equals( + options.types[0], + 'SubExpression', + 'string params for outer helper processed correctly' + ); + equals( + options.types[1], + 'PathExpression', + 'string params for outer helper processed correctly' + ); + return a + b; + }, - blorg: function(a, options) { - equals( - options.types.length, - 1, - 'string params for inner helper processed correctly' - ); - equals( - options.types[0], - 'PathExpression', - 'string params for inner helper processed correctly' - ); - return a; - } - }; - - var input = { - foo: {}, - yeah: {} - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) - .withInput(input) + blorg: function(a, options) { + equals( + options.types.length, + 1, + 'string params for inner helper processed correctly' + ); + equals( + options.types[0], + 'PathExpression', + 'string params for inner helper processed correctly' + ); + return a; + } + }) + .withInput({ + foo: {}, + yeah: {} + }) .toCompileTo('fooyeah'); }); it('as hashes in string params mode', function() { - var string = '{{blog fun=(bork)}}'; - var compileOptions = { stringParams: true }; - - var helpers = { - blog: function(options) { - equals(options.hashTypes.fun, 'SubExpression'); - return 'val is ' + options.hash.fun; - }, - bork: function() { - return 'BORK'; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{blog fun=(bork)}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + blog: function(options) { + equals(options.hashTypes.fun, 'SubExpression'); + return 'val is ' + options.hash.fun; + }, + bork: function() { + return 'BORK'; + } + }) .toCompileTo('val is BORK'); }); it('subexpression functions on the context', function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: function() { - return 'LOL'; - } - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: function() { + return 'LOL'; + } + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) .toCompileTo('LOLLOL!'); }); it("subexpressions can't just be property lookups", function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: 'LOL' - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - expectTemplate(string) - .withInput(context) - .withHelpers(helpers) + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: 'LOL' + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) .toThrow(); }); }); diff --git a/spec/track-ids.js b/spec/track-ids.js index 72713d43b..033e7b176 100644 --- a/spec/track-ids.js +++ b/spec/track-ids.js @@ -5,216 +5,182 @@ describe('track ids', function() { }); it('should not include anything without the flag', function() { - var string = '{{wycats is.a slave.driver}}'; - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids, undefined); - equal(options.hashIds, undefined); - - return 'success'; - } - }; - - expectTemplate(string) - .withHelpers(helpers) + expectTemplate('{{wycats is.a slave.driver}}') + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids, undefined); + equal(options.hashIds, undefined); + + return 'success'; + } + }) .toCompileTo('success'); }); - it('should include argument ids', function() { - var string = '{{wycats is.a slave.driver}}'; - var compileOptions = { trackIds: true }; - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + it('should include argument ids', function() { + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should include hash ids', function() { - var string = '{{wycats bat=is.a baz=slave.driver}}'; - var compileOptions = { trackIds: true }; - var helpers = { - wycats: function(options) { - equal(options.hashIds.bat, 'is.a'); - equal(options.hashIds.baz, 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.hashIds.bat + - ':' + - options.hash.bat + - ' ' + - options.hashIds.baz + - ':' + - options.hash.baz - ); - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + it('should include hash ids', function() { + expectTemplate('{{wycats bat=is.a baz=slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(options) { + equal(options.hashIds.bat, 'is.a'); + equal(options.hashIds.baz, 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.hashIds.bat + + ':' + + options.hash.bat + + ' ' + + options.hashIds.baz + + ':' + + options.hash.baz + ); + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should note ../ and ./ references', function() { - var string = '{{wycats ./is.a ../slave.driver this.is.a this}}'; - var compileOptions = { trackIds: true }; - - var helpers = { - wycats: function(passiveVoice, noun, thiz, thiz2, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], '../slave.driver'); - equal(options.ids[2], 'is.a'); - equal(options.ids[3], ''); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + it('should note ../ and ./ references', function() { + expectTemplate('{{wycats ./is.a ../slave.driver this.is.a this}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, thiz, thiz2, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], '../slave.driver'); + equal(options.ids[2], 'is.a'); + equal(options.ids[3], ''); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS is.a:foo ../slave.driver:undefined'); }); - it('should note @data references', function() { - var string = '{{wycats @is.a @slave.driver}}'; - var compileOptions = { trackIds: true }; - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], '@is.a'); - equal(options.ids[1], '@slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + it('should note @data references', function() { + expectTemplate('{{wycats @is.a @slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], '@is.a'); + equal(options.ids[1], '@slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) .withRuntimeOptions({ data: context }) .toCompileTo('HELP ME MY BOSS @is.a:foo @slave.driver:bar'); }); it('should return null for constants', function() { - var string = '{{wycats 1 "foo" key=false}}'; - var compileOptions = { trackIds: true }; - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], null); - equal(options.ids[1], null); - equal(options.hashIds.key, null); - - return ( - 'HELP ME MY BOSS ' + - passiveVoice + - ' ' + - noun + - ' ' + - options.hash.key - ); - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{wycats 1 "foo" key=false}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], null); + equal(options.ids[1], null); + equal(options.hashIds.key, null); + + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ' ' + + options.hash.key + ); + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS 1 foo false'); }); - it('should return true for subexpressions', function() { - var string = '{{wycats (sub)}}'; - var compileOptions = { trackIds: true }; - var helpers = { - sub: function() { - return 1; - }, - wycats: function(passiveVoice, options) { - equal(options.ids[0], true); - - return 'HELP ME MY BOSS ' + passiveVoice; - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + it('should return true for subexpressions', function() { + expectTemplate('{{wycats (sub)}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + sub: function() { + return 1; + }, + wycats: function(passiveVoice, options) { + equal(options.ids[0], true); + + return 'HELP ME MY BOSS ' + passiveVoice; + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS 1'); }); it('should use block param paths', function() { - var string = '{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}'; - var compileOptions = { trackIds: true }; - - var helpers = { - doIt: function(options) { - var blockParams = [this.is]; - blockParams.path = ['zomg']; - return options.fn(this, { blockParams: blockParams }); - }, - wycats: function(passiveVoice, noun, blah, options) { - equal(options.ids[0], 'zomg.a'); - equal(options.ids[1], 'slave.driver'); - equal(options.ids[2], 'zomg'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - expectTemplate(string) - .withCompileOptions(compileOptions) - .withHelpers(helpers) + expectTemplate('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + doIt: function(options) { + var blockParams = [this.is]; + blockParams.path = ['zomg']; + return options.fn(this, { blockParams: blockParams }); + }, + wycats: function(passiveVoice, noun, blah, options) { + equal(options.ids[0], 'zomg.a'); + equal(options.ids[1], 'slave.driver'); + equal(options.ids[2], 'zomg'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) .withInput(context) .toCompileTo('HELP ME MY BOSS zomg.a:foo slave.driver:bar'); }); @@ -231,108 +197,84 @@ describe('track ids', function() { describe('#each', function() { it('should track contextPath for arrays', function() { - var string = '{{#each array}}{{wycats name}}{{/each}}'; - var compileOptions = { trackIds: true }; - - var input = { array: [{ name: 'foo' }, { name: 'bar' }] }; - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate('{{#each array}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) .toCompileTo('foo:array.0\nbar:array.1\n'); }); - it('should track contextPath for keys', function() { - var string = '{{#each object}}{{wycats name}}{{/each}}'; - var compileOptions = { trackIds: true }; - var input = { object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should track contextPath for keys', function() { + expectTemplate('{{#each object}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }) .toCompileTo('foo:object.foo\nbar:object.bar\n'); }); - it('should handle nesting', function() { - var string = '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}'; - var compileOptions = { trackIds: true }; - var input = { array: [{ name: 'foo' }, { name: 'bar' }] }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should handle nesting', function() { + expectTemplate( + '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) .toCompileTo('foo:.array..0\nbar:.array..1\n'); }); - it('should handle block params', function() { - var string = - '{{#each array as |value|}}{{blockParams value.name}}{{/each}}'; - var compileOptions = { trackIds: true }; - var input = { array: [{ name: 'foo' }, { name: 'bar' }] }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should handle block params', function() { + expectTemplate( + '{{#each array as |value|}}{{blockParams value.name}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) .toCompileTo('foo:array.0.name\nbar:array.1.name\n'); }); }); + describe('#with', function() { it('should track contextPath', function() { - var string = '{{#with field}}{{wycats name}}{{/with}}'; - var compileOptions = { trackIds: true }; - - var input = { field: { name: 'foo' } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate('{{#with field}}{{wycats name}}{{/with}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ field: { name: 'foo' } }) .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var string = - '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}'; - var compileOptions = { trackIds: true }; - var input = { bat: { field: { name: 'foo' } } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should handle nesting', function() { + expectTemplate( + '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}' + ) + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ bat: { field: { name: 'foo' } } }) .toCompileTo('foo:bat.field\n'); }); }); + describe('#blockHelperMissing', function() { it('should track contextPath for arrays', function() { - var string = '{{#field}}{{wycats name}}{{/field}}'; - var compileOptions = { trackIds: true }; - - var input = { field: [{ name: 'foo' }] }; - expectTemplate(string) - .withCompileOptions(compileOptions) + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ field: [{ name: 'foo' }] }) .toCompileTo('foo:field.0\n'); }); - it('should track contextPath for keys', function() { - var string = '{{#field}}{{wycats name}}{{/field}}'; - var compileOptions = { trackIds: true }; - var input = { field: { name: 'foo' } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should track contextPath for keys', function() { + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ field: { name: 'foo' } }) .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var string = '{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}'; - var compileOptions = { trackIds: true }; - var input = { bat: { field: { name: 'foo' } } }; - expectTemplate(string) - .withCompileOptions(compileOptions) + it('should handle nesting', function() { + expectTemplate('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}') + .withCompileOptions({ trackIds: true }) .withHelpers(helpers) - .withInput(input) + .withInput({ bat: { field: { name: 'foo' } } }) .toCompileTo('foo:bat.field\n'); }); }); diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index 0e7f74221..f826d95a1 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -5,9 +5,11 @@ describe('whitespace control', function() { expectTemplate(' {{~foo~}} ') .withInput(hash) .toCompileTo('bar<'); + expectTemplate(' {{~foo}} ') .withInput(hash) .toCompileTo('bar< '); + expectTemplate(' {{foo~}} ') .withInput(hash) .toCompileTo(' bar<'); @@ -15,6 +17,7 @@ describe('whitespace control', function() { expectTemplate(' {{~&foo~}} ') .withInput(hash) .toCompileTo('bar<'); + expectTemplate(' {{~{foo}~}} ') .withInput(hash) .toCompileTo('bar<'); @@ -29,12 +32,15 @@ describe('whitespace control', function() { expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') .withInput(hash) .toCompileTo('bar'); + expectTemplate(' {{#if foo~}} bar {{/if~}} ') .withInput(hash) .toCompileTo(' bar '); + expectTemplate(' {{~#if foo}} bar {{~/if}} ') .withInput(hash) .toCompileTo(' bar '); + expectTemplate(' {{#if foo}} bar {{/if}} ') .withInput(hash) .toCompileTo(' bar '); @@ -42,42 +48,41 @@ describe('whitespace control', function() { expectTemplate(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ') .withInput(hash) .toCompileTo('bar'); + expectTemplate(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ') .withInput(hash) .toCompileTo(' abara '); }); + it('should strip whitespace around inverse block calls', function() { - var hash = {}; + expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar'); - expectTemplate(' {{~^if foo~}} bar {{~/if~}} ') - .withInput(hash) - .toCompileTo('bar'); - expectTemplate(' {{^if foo~}} bar {{/if~}} ') - .withInput(hash) - .toCompileTo(' bar '); - expectTemplate(' {{~^if foo}} bar {{~/if}} ') - .withInput(hash) - .toCompileTo(' bar '); - expectTemplate(' {{^if foo}} bar {{/if}} ') - .withInput(hash) - .toCompileTo(' bar '); + expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar '); - expectTemplate(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ') - .withInput(hash) - .toCompileTo('bar'); + expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar '); + + expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo(' bar '); + + expectTemplate( + ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ' + ).toCompileTo('bar'); }); + it('should strip whitespace around complex block calls', function() { var hash = { foo: 'bar<' }; expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') .withInput(hash) .toCompileTo('bar'); + expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') .withInput(hash) .toCompileTo('bar '); + expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') .withInput(hash) .toCompileTo(' bar'); + expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') .withInput(hash) .toCompileTo(' bar '); @@ -91,36 +96,34 @@ describe('whitespace control', function() { ) .withInput(hash) .toCompileTo('bar'); + expectTemplate( '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' ) .withInput(hash) .toCompileTo('bar<'); - hash = {}; + expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo( + 'baz' + ); - expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') - .withInput(hash) - .toCompileTo('baz'); - expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}') - .withInput(hash) - .toCompileTo('baz '); - expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}') - .withInput(hash) - .toCompileTo(' baz'); - expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}') - .withInput(hash) - .toCompileTo(' baz '); + expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz '); - expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') - .withInput(hash) - .toCompileTo('baz'); + expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo( + ' baz' + ); + + expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo( + ' baz ' + ); + + expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo( + 'baz' + ); expectTemplate( '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' - ) - .withInput(hash) - .toCompileTo('baz'); + ).toCompileTo('baz'); }); }); @@ -128,9 +131,11 @@ describe('whitespace control', function() { expectTemplate('foo {{~> dude~}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foobar'); + expectTemplate('foo {{> dude~}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foo bar'); + expectTemplate('foo {{> dude}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foo bar '); @@ -138,16 +143,15 @@ describe('whitespace control', function() { expectTemplate('foo\n {{~> dude}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foobar'); + expectTemplate('foo\n {{> dude}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foo\n bar'); }); it('should only strip whitespace once', function() { - var hash = { foo: 'bar' }; - expectTemplate(' {{~foo~}} {{foo}} {{foo}} ') - .withInput(hash) + .withInput({ foo: 'bar' }) .toCompileTo('barbar bar '); }); });