global.handlebarsEnv = null; beforeEach(function() { global.handlebarsEnv = Handlebars.create(); }); describe('basic context', function() { it('most basic', function() { expectTemplate('{{foo}}') .withInput({ foo: 'foo' }) .toCompileTo('foo'); }); it('escaping', 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'); }); it('compiling with a basic context', function() { expectTemplate('Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', world: 'world' }) .withMessage('It works if all the required keys are provided') .toCompileTo('Goodbye\ncruel\nworld!'); }); it('compiling with a string context', function() { expectTemplate('{{.}}{{length}}') .withInput('bye') .toCompileTo('bye3'); }); it('compiling with an undefined context', function() { expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') .withInput(undefined) .toCompileTo('Goodbye\n\n!'); expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}') .withInput(undefined) .toCompileTo('Goodbye'); }); it('comments', function() { expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', world: 'world' }) .withMessage('comments are ignored') .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' ); }); it('boolean', function() { var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; expectTemplate(string) .withInput({ goodbye: true, world: 'world' }) .withMessage('booleans show the contents when true') .toCompileTo('GOODBYE cruel world!'); expectTemplate(string) .withInput({ goodbye: false, world: 'world' }) .withMessage('booleans do not show the contents when false') .toCompileTo('cruel world!'); }); it('zeros', function() { expectTemplate('num1: {{num1}}, num2: {{num2}}') .withInput({ num1: 42, 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}}') .withInput({ val1: false, 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'); expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}') .withInput({ val1: false, val2: new Boolean(false) }) .toCompileTo('val1: false, val2: false'); expectTemplate('val: {{{val1/val2}}}') .withInput({ val1: { val2: false } }) .toCompileTo('val: false'); /* eslint-enable */ }); it('should handle undefined and null', function() { expectTemplate('{{awesome undefined null}}') .withInput({ awesome: function(_undefined, _null, options) { return ( (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options ); } }) .toCompileTo('true true object'); expectTemplate('{{undefined}}') .withInput({ undefined: function() { return 'undefined!'; } }) .toCompileTo('undefined!'); expectTemplate('{{null}}') .withInput({ null: function() { return 'null!'; } }) .toCompileTo('null!'); }); it('newlines', function() { expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); }); it('escaping text', function() { expectTemplate("Awesome's") .withMessage( "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(" ' ' "); }); it('escaping expressions', function() { expectTemplate('{{{awesome}}}') .withInput({ awesome: "&'\\<>" }) .withMessage("expressions with 3 handlebars aren't escaped") .toCompileTo("&'\\<>"); expectTemplate('{{&awesome}}') .withInput({ awesome: "&'\\<>" }) .withMessage("expressions with {{& handlebars aren't escaped") .toCompileTo("&'\\<>"); expectTemplate('{{awesome}}') .withInput({ awesome: '&"\'`\\<>' }) .withMessage('by default expressions should be escaped') .toCompileTo('&"'`\\<>'); expectTemplate('{{awesome}}') .withInput({ awesome: 'Escaped, looks like: <b>' }) .withMessage('escaping should properly handle amperstands') .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); }); it("functions returning safestrings shouldn't be escaped", function() { expectTemplate('{{awesome}}') .withInput({ awesome: function() { return new Handlebars.SafeString("&'\\<>"); } }) .withMessage("functions returning safestrings aren't escaped") .toCompileTo("&'\\<>"); }); it('functions', function() { expectTemplate('{{awesome}}') .withInput({ awesome: function() { return 'Awesome'; } }) .withMessage('functions are called and render their output') .toCompileTo('Awesome'); expectTemplate('{{awesome}}') .withInput({ awesome: function() { return this.more; }, more: 'More awesome' }) .withMessage('functions are bound to the context') .toCompileTo('More awesome'); }); it('functions with context argument', function() { expectTemplate('{{awesome frank}}') .withInput({ awesome: function(context) { return context; }, frank: 'Frank' }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); it('pathed functions with context argument', function() { expectTemplate('{{bar.awesome frank}}') .withInput({ bar: { awesome: function(context) { return context; } }, frank: 'Frank' }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); it('depthed functions with context argument', function() { expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') .withInput({ awesome: function(context) { return context; }, frank: 'Frank' }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); it('block functions with context argument', function() { expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') .withInput({ awesome: function(context, options) { return options.fn(context); } }) .withMessage('block functions are called with context and options') .toCompileTo('inner 1'); }); it('depthed block functions with context argument', function() { expectTemplate( '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' ) .withInput({ value: true, awesome: function(context, options) { return options.fn(context); } }) .withMessage('block functions are called with context and options') .toCompileTo('inner 1'); }); it('block functions without context argument', function() { expectTemplate('{{#awesome}}inner{{/awesome}}') .withInput({ awesome: function(options) { return options.fn(this); } }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); it('pathed block functions without context argument', function() { expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') .withInput({ foo: { awesome: function() { return this; } } }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); it('depthed block functions without context argument', function() { expectTemplate( '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' ) .withInput({ value: true, awesome: function() { return this; } }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); it('paths with hyphens', function() { expectTemplate('{{foo-bar}}') .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 (-)') .toCompileTo('baz'); }); it('nested paths', function() { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: 'beautiful' } }) .withMessage('Nested paths access nested objects') .toCompileTo('Goodbye beautiful world!'); }); it('nested paths with empty string value', function() { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: '' } }) .withMessage('Nested paths access nested objects with empty string') .toCompileTo('Goodbye world!'); }); it('literal paths', function() { expectTemplate('Goodbye {{[@alan]/expression}} world!') .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') .toCompileTo('Goodbye beautiful world!'); }); it('literal references', 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!'); }); it("that current context path ({{.}}) doesn't hit helpers", function() { expectTemplate('test: {{.}}') .withInput(null) .withHelpers({ helper: 'awesome' }) .toCompileTo('test: '); }); it('complex but empty paths', function() { expectTemplate('{{person/name}}') .withInput({ person: { name: null } }) .toCompileTo(''); expectTemplate('{{person/name}}') .withInput({ person: {} }) .toCompileTo(''); }); it('this keyword in paths', function() { expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .withMessage('This keyword in paths evaluates to current context') .toCompileTo('goodbyeGoodbyeGOODBYE'); expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') .withInput({ hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] }) .withMessage('This keyword evaluates in more complex paths') .toCompileTo('helloHelloHELLO'); }); it('this keyword nested inside path', function() { expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:13' ); expectTemplate('{{[this]}}') .withInput({ this: 'bar' }) .toCompileTo('bar'); expectTemplate('{{text/[this]}}') .withInput({ text: { this: 'bar' } }) .toCompileTo('bar'); }); it('this keyword in helpers', function() { var helpers = { foo: function(value) { return 'bar ' + value; } }; 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'); 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() { expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:17' ); expectTemplate('{{foo [this]}}') .withInput({ foo: function(value) { return value; }, this: 'bar' }) .toCompileTo('bar'); expectTemplate('{{foo text/[this]}}') .withInput({ foo: function(value) { return value; }, text: { this: 'bar' } }) .toCompileTo('bar'); }); it('pass string literals', function() { expectTemplate('{{"foo"}}').toCompileTo(''); expectTemplate('{{"foo"}}') .withInput({ foo: 'bar' }) .toCompileTo('bar'); expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') .withInput({ foo: ['bar', 'baz'] }) .toCompileTo('barbaz'); }); 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) { return 'bar' + arg; } }) .toCompileTo('bar1'); }); 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() { expectTemplate('{{foo (false)}}') .withInput({ false: function() { return 'bar'; } }) .withHelper('foo', function(arg) { return arg; }) .toCompileTo('bar'); }); });