global.handlebarsEnv = null; beforeEach(function() { global.handlebarsEnv = Handlebars.create(); }); describe('basic context', function() { it('most basic', function() { shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo'); }); it('escaping', function() { shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}'); shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}'); shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food'); shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food'); shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food'); }); it('compiling with a basic context', function() { shouldCompileTo( 'Goodbye\n{{cruel}}\n{{world}}!', { cruel: 'cruel', world: 'world' }, 'Goodbye\ncruel\nworld!', 'It works if all the required keys are provided' ); }); it('compiling with a string context', function() { shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3'); }); it('compiling with an undefined context', function() { shouldCompileTo( 'Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!' ); shouldCompileTo( '{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye' ); }); it('comments', function() { shouldCompileTo( '{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!', { cruel: 'cruel', world: 'world' }, 'Goodbye\ncruel\nworld!', 'comments are ignored' ); shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah'); shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah'); shouldCompileTo(' {{! comment ~}} blah', {}, ' blah'); shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah'); shouldCompileTo(' {{~! comment}} blah', {}, ' blah'); shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah'); }); it('boolean', function() { var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; shouldCompileTo( string, { goodbye: true, world: 'world' }, 'GOODBYE cruel world!', 'booleans show the contents when true' ); shouldCompileTo( string, { goodbye: false, world: 'world' }, 'cruel world!', 'booleans do not show the contents when false' ); }); it('zeros', function() { shouldCompileTo( 'num1: {{num1}}, num2: {{num2}}', { num1: 42, num2: 0 }, 'num1: 42, num2: 0' ); shouldCompileTo('num: {{.}}', 0, 'num: 0'); shouldCompileTo('num: {{num1/num2}}', { num1: { num2: 0 } }, 'num: 0'); }); it('false', function() { /* eslint-disable no-new-wrappers */ shouldCompileTo( 'val1: {{val1}}, val2: {{val2}}', { val1: false, val2: new Boolean(false) }, 'val1: false, val2: false' ); shouldCompileTo('val: {{.}}', false, 'val: false'); shouldCompileTo( 'val: {{val1/val2}}', { val1: { val2: false } }, 'val: false' ); shouldCompileTo( 'val1: {{{val1}}}, val2: {{{val2}}}', { val1: false, val2: new Boolean(false) }, 'val1: false, val2: false' ); shouldCompileTo( 'val: {{{val1/val2}}}', { val1: { val2: false } }, 'val: false' ); /* eslint-enable */ }); it('should handle undefined and null', function() { shouldCompileTo( '{{awesome undefined null}}', { awesome: function(_undefined, _null, options) { return ( (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options ); } }, 'true true object' ); shouldCompileTo( '{{undefined}}', { undefined: function() { return 'undefined!'; } }, 'undefined!' ); shouldCompileTo( '{{null}}', { null: function() { return 'null!'; } }, 'null!' ); }); it('newlines', function() { shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); }); it('escaping text', function() { shouldCompileTo( "Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes" ); shouldCompileTo( 'Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored" ); shouldCompileTo( 'Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes" ); shouldCompileTo( 'Awesome {{foo}}', { foo: '\\' }, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes" ); shouldCompileTo( " ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript' ); }); it('escaping expressions', function() { shouldCompileTo( '{{{awesome}}}', { awesome: "&'\\<>" }, "&'\\<>", "expressions with 3 handlebars aren't escaped" ); shouldCompileTo( '{{&awesome}}', { awesome: "&'\\<>" }, "&'\\<>", "expressions with {{& handlebars aren't escaped" ); shouldCompileTo( '{{awesome}}', { awesome: '&"\'`\\<>' }, '&"'`\\<>', 'by default expressions should be escaped' ); shouldCompileTo( '{{awesome}}', { awesome: 'Escaped, looks like: <b>' }, 'Escaped, <b> looks like: &lt;b&gt;', 'escaping should properly handle amperstands' ); }); it("functions returning safestrings shouldn't be escaped", function() { var hash = { awesome: function() { return new Handlebars.SafeString("&'\\<>"); } }; shouldCompileTo( '{{awesome}}', hash, "&'\\<>", "functions returning safestrings aren't escaped" ); }); it('functions', function() { shouldCompileTo( '{{awesome}}', { awesome: function() { return 'Awesome'; } }, 'Awesome', 'functions are called and render their output' ); shouldCompileTo( '{{awesome}}', { awesome: function() { return this.more; }, more: 'More awesome' }, 'More awesome', 'functions are bound to the context' ); }); it('functions with context argument', function() { shouldCompileTo( '{{awesome frank}}', { awesome: function(context) { return context; }, frank: 'Frank' }, 'Frank', 'functions are called with context arguments' ); }); it('pathed functions with context argument', function() { shouldCompileTo( '{{bar.awesome frank}}', { bar: { awesome: function(context) { return context; } }, frank: 'Frank' }, 'Frank', 'functions are called with context arguments' ); }); it('depthed functions with context argument', function() { shouldCompileTo( '{{#with frank}}{{../awesome .}}{{/with}}', { awesome: function(context) { return context; }, frank: 'Frank' }, 'Frank', 'functions are called with context arguments' ); }); it('block functions with context argument', function() { shouldCompileTo( '{{#awesome 1}}inner {{.}}{{/awesome}}', { awesome: function(context, options) { return options.fn(context); } }, 'inner 1', 'block functions are called with context and options' ); }); it('depthed block functions with context argument', function() { shouldCompileTo( '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}', { value: true, awesome: function(context, options) { return options.fn(context); } }, 'inner 1', 'block functions are called with context and options' ); }); it('block functions without context argument', function() { shouldCompileTo( '{{#awesome}}inner{{/awesome}}', { awesome: function(options) { return options.fn(this); } }, 'inner', 'block functions are called with options' ); }); it('pathed block functions without context argument', function() { shouldCompileTo( '{{#foo.awesome}}inner{{/foo.awesome}}', { foo: { awesome: function() { return this; } } }, 'inner', 'block functions are called with options' ); }); it('depthed block functions without context argument', function() { shouldCompileTo( '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}', { value: true, awesome: function() { return this; } }, 'inner', 'block functions are called with options' ); }); it('paths with hyphens', function() { shouldCompileTo( '{{foo-bar}}', { 'foo-bar': 'baz' }, 'baz', 'Paths can contain hyphens (-)' ); shouldCompileTo( '{{foo.foo-bar}}', { foo: { 'foo-bar': 'baz' } }, 'baz', 'Paths can contain hyphens (-)' ); shouldCompileTo( '{{foo/foo-bar}}', { foo: { 'foo-bar': 'baz' } }, 'baz', 'Paths can contain hyphens (-)' ); }); it('nested paths', function() { shouldCompileTo( 'Goodbye {{alan/expression}} world!', { alan: { expression: 'beautiful' } }, 'Goodbye beautiful world!', 'Nested paths access nested objects' ); }); it('nested paths with empty string value', function() { shouldCompileTo( 'Goodbye {{alan/expression}} world!', { alan: { expression: '' } }, 'Goodbye world!', 'Nested paths access nested objects with empty string' ); }); it('literal paths', function() { shouldCompileTo( 'Goodbye {{[@alan]/expression}} world!', { '@alan': { expression: 'beautiful' } }, 'Goodbye beautiful world!', 'Literal paths can be used' ); shouldCompileTo( 'Goodbye {{[foo bar]/expression}} world!', { 'foo bar': { expression: 'beautiful' } }, 'Goodbye beautiful world!', 'Literal paths can be used' ); }); it('literal references', function() { shouldCompileTo( 'Goodbye {{[foo bar]}} world!', { 'foo bar': 'beautiful' }, 'Goodbye beautiful world!' ); shouldCompileTo( 'Goodbye {{"foo bar"}} world!', { 'foo bar': 'beautiful' }, 'Goodbye beautiful world!' ); shouldCompileTo( "Goodbye {{'foo bar'}} world!", { 'foo bar': 'beautiful' }, 'Goodbye beautiful world!' ); shouldCompileTo( 'Goodbye {{"foo[bar"}} world!', { 'foo[bar': 'beautiful' }, 'Goodbye beautiful world!' ); shouldCompileTo( 'Goodbye {{"foo\'bar"}} world!', { "foo'bar": 'beautiful' }, 'Goodbye beautiful world!' ); shouldCompileTo( "Goodbye {{'foo\"bar'}} world!", { 'foo"bar': 'beautiful' }, 'Goodbye beautiful world!' ); }); it("that current context path ({{.}}) doesn't hit helpers", function() { shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: '); }); it('complex but empty paths', function() { shouldCompileTo('{{person/name}}', { person: { name: null } }, ''); shouldCompileTo('{{person/name}}', { person: {} }, ''); }); it('this keyword in paths', function() { var string = '{{#goodbyes}}{{this}}{{/goodbyes}}'; var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }; shouldCompileTo( string, hash, 'goodbyeGoodbyeGOODBYE', 'This keyword in paths evaluates to current context' ); string = '{{#hellos}}{{this/text}}{{/hellos}}'; hash = { hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] }; shouldCompileTo( string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths' ); }); it('this keyword nested inside path', function() { shouldThrow( function() { CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}'); }, Error, 'Invalid path: text/this - 1:13' ); shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar'); shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar'); }); it('this keyword in helpers', function() { var helpers = { foo: function(value) { return 'bar ' + value; } }; var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}'; var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }; shouldCompileTo( string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE', 'This keyword in paths evaluates to current context' ); string = '{{#hellos}}{{foo this/text}}{{/hellos}}'; hash = { hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] }; shouldCompileTo( string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths' ); }); it('this keyword nested inside helpers param', function() { var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; shouldThrow( function() { CompilerContext.compile(string); }, Error, 'Invalid path: text/this - 1:17' ); shouldCompileTo( '{{foo [this]}}', { foo: function(value) { return value; }, this: 'bar' }, 'bar' ); shouldCompileTo( '{{foo text/[this]}}', { foo: function(value) { return value; }, text: { this: 'bar' } }, 'bar' ); }); it('pass string literals', function() { shouldCompileTo('{{"foo"}}', {}, ''); shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar'); shouldCompileTo( '{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz' ); }); it('pass number literals', function() { shouldCompileTo('{{12}}', {}, ''); shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar'); shouldCompileTo('{{12.34}}', {}, ''); shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar'); shouldCompileTo( '{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1' ); }); it('pass boolean literals', function() { shouldCompileTo('{{true}}', {}, ''); shouldCompileTo('{{true}}', { '': 'foo' }, ''); shouldCompileTo('{{false}}', { false: 'foo' }, 'foo'); }); it('should handle literals in subexpression', function() { var helpers = { foo: function(arg) { return arg; } }; shouldCompileTo( '{{foo (false)}}', [ { false: function() { return 'bar'; } }, helpers ], 'bar' ); }); });