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'); }); });