diff --git a/spec/basic.js b/spec/basic.js index 65d228729..4c7afb706 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -6,117 +6,156 @@ beforeEach(function() { describe('basic context', function() { it('most basic', function() { - shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo'); + expectTemplate('{{foo}}') + .withInput({ foo: 'foo' }) + .toCompileTo('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'); + 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() { - shouldCompileTo( - 'Goodbye\n{{cruel}}\n{{world}}!', - { cruel: 'cruel', world: 'world' }, - 'Goodbye\ncruel\nworld!', - 'It works if all the required keys are provided' - ); + 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() { - shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3'); + expectTemplate('{{.}}{{length}}') + .withInput('bye') + .toCompileTo('bye3'); }); it('compiling with an undefined context', function() { - shouldCompileTo( - 'Goodbye\n{{cruel}}\n{{world.bar}}!', - undefined, - 'Goodbye\n\n!' - ); + expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') + .withInput(undefined) + .toCompileTo('Goodbye\n\n!'); - shouldCompileTo( - '{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', - undefined, - 'Goodbye' - ); + expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}') + .withInput(undefined) + .toCompileTo('Goodbye'); }); it('comments', function() { - shouldCompileTo( - '{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!', - { cruel: 'cruel', world: 'world' }, - 'Goodbye\ncruel\nworld!', - 'comments are ignored' + 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' ); - 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'); - }); + expectTemplate(' {{! comment ~}} blah').toCompileTo(' 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' + expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo( + ' blah' ); - shouldCompileTo( - string, - { goodbye: false, world: 'world' }, - 'cruel world!', - 'booleans do not show the contents when false' + 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() { - 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'); + 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 */ - 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' - ); + 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() { - shouldCompileTo( - '{{awesome undefined null}}', - { + expectTemplate('{{awesome undefined null}}') + .withInput({ awesome: function(_undefined, _null, options) { return ( (_undefined === undefined) + @@ -126,373 +165,325 @@ describe('basic context', function() { typeof options ); } - }, - 'true true object' - ); - shouldCompileTo( - '{{undefined}}', - { + }) + .toCompileTo('true true object'); + + expectTemplate('{{undefined}}') + .withInput({ undefined: function() { return 'undefined!'; } - }, - 'undefined!' - ); - shouldCompileTo( - '{{null}}', - { + }) + .toCompileTo('undefined!'); + + expectTemplate('{{null}}') + .withInput({ null: function() { return 'null!'; } - }, - 'null!' - ); + }) + .toCompileTo('null!'); }); it('newlines', function() { - shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); - shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); + expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); + + expectTemplate("Alan's\rTest").toCompileTo("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' - ); + 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() { - shouldCompileTo( - '{{{awesome}}}', - { awesome: "&'\\<>" }, - "&'\\<>", - "expressions with 3 handlebars aren't escaped" - ); + expectTemplate('{{{awesome}}}') + .withInput({ awesome: "&'\\<>" }) + .withMessage("expressions with 3 handlebars aren't escaped") + .toCompileTo("&'\\<>"); - shouldCompileTo( - '{{&awesome}}', - { awesome: "&'\\<>" }, - "&'\\<>", - "expressions with {{& handlebars aren't escaped" - ); + expectTemplate('{{&awesome}}') + .withInput({ awesome: "&'\\<>" }) + .withMessage("expressions with {{& handlebars aren't escaped") + .toCompileTo("&'\\<>"); - shouldCompileTo( - '{{awesome}}', - { awesome: '&"\'`\\<>' }, - '&"'`\\<>', - 'by default expressions should be escaped' - ); + expectTemplate('{{awesome}}') + .withInput({ awesome: '&"\'`\\<>' }) + .withMessage('by default expressions should be escaped') + .toCompileTo('&"'`\\<>'); - shouldCompileTo( - '{{awesome}}', - { awesome: 'Escaped, looks like: <b>' }, - 'Escaped, <b> looks like: <b>', - 'escaping should properly handle amperstands' - ); + expectTemplate('{{awesome}}') + .withInput({ awesome: 'Escaped, looks like: <b>' }) + .withMessage('escaping should properly handle amperstands') + .toCompileTo('Escaped, <b> looks like: <b>'); }); 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" - ); + expectTemplate('{{awesome}}') + .withInput({ + awesome: function() { + return new Handlebars.SafeString("&'\\<>"); + } + }) + .withMessage("functions returning safestrings aren't escaped") + .toCompileTo("&'\\<>"); }); it('functions', function() { - shouldCompileTo( - '{{awesome}}', - { + expectTemplate('{{awesome}}') + .withInput({ awesome: function() { return 'Awesome'; } - }, - 'Awesome', - 'functions are called and render their output' - ); - shouldCompileTo( - '{{awesome}}', - { + }) + .withMessage('functions are called and render their output') + .toCompileTo('Awesome'); + + expectTemplate('{{awesome}}') + .withInput({ awesome: function() { return this.more; }, more: 'More awesome' - }, - 'More awesome', - 'functions are bound to the context' - ); + }) + .withMessage('functions are bound to the context') + .toCompileTo('More awesome'); }); it('functions with context argument', function() { - shouldCompileTo( - '{{awesome frank}}', - { + expectTemplate('{{awesome frank}}') + .withInput({ awesome: function(context) { return context; }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); + it('pathed functions with context argument', function() { - shouldCompileTo( - '{{bar.awesome frank}}', - { + expectTemplate('{{bar.awesome frank}}') + .withInput({ bar: { awesome: function(context) { return context; } }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); + it('depthed functions with context argument', function() { - shouldCompileTo( - '{{#with frank}}{{../awesome .}}{{/with}}', - { + expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') + .withInput({ awesome: function(context) { return context; }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); it('block functions with context argument', function() { - shouldCompileTo( - '{{#awesome 1}}inner {{.}}{{/awesome}}', - { + expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') + .withInput({ awesome: function(context, options) { return options.fn(context); } - }, - 'inner 1', - 'block functions are called with context and options' - ); + }) + .withMessage('block functions are called with context and options') + .toCompileTo('inner 1'); }); it('depthed block functions with context argument', function() { - shouldCompileTo( - '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}', - { + expectTemplate( + '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' + ) + .withInput({ value: true, awesome: function(context, options) { return options.fn(context); } - }, - 'inner 1', - 'block functions are called with context and options' - ); + }) + .withMessage('block functions are called with context and options') + .toCompileTo('inner 1'); }); it('block functions without context argument', function() { - shouldCompileTo( - '{{#awesome}}inner{{/awesome}}', - { + expectTemplate('{{#awesome}}inner{{/awesome}}') + .withInput({ awesome: function(options) { return options.fn(this); } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); + it('pathed block functions without context argument', function() { - shouldCompileTo( - '{{#foo.awesome}}inner{{/foo.awesome}}', - { + expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') + .withInput({ foo: { awesome: function() { return this; } } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); + it('depthed block functions without context argument', function() { - shouldCompileTo( - '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}', - { + expectTemplate( + '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' + ) + .withInput({ value: true, awesome: function() { return this; } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); 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 (-)' - ); + 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() { - shouldCompileTo( - 'Goodbye {{alan/expression}} world!', - { alan: { expression: 'beautiful' } }, - 'Goodbye beautiful world!', - 'Nested paths access nested objects' - ); + 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() { - shouldCompileTo( - 'Goodbye {{alan/expression}} world!', - { alan: { expression: '' } }, - 'Goodbye world!', - 'Nested paths access nested objects with empty string' - ); + expectTemplate('Goodbye {{alan/expression}} world!') + .withInput({ alan: { expression: '' } }) + .withMessage('Nested paths access nested objects with empty string') + .toCompileTo('Goodbye world!'); }); 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' - ); + 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() { - 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!' - ); + 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() { - shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: '); + expectTemplate('test: {{.}}') + .withInput(null) + .withHelpers({ helper: 'awesome' }) + .toCompileTo('test: '); }); it('complex but empty paths', function() { - shouldCompileTo('{{person/name}}', { person: { name: null } }, ''); - shouldCompileTo('{{person/name}}', { person: {} }, ''); + 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'] }; - shouldCompileTo( - string, - hash, - 'goodbyeGoodbyeGOODBYE', - 'This keyword in paths evaluates to current context' - ); + 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' }] - }; - shouldCompileTo( - string, - hash, - 'helloHelloHELLO', - 'This keyword evaluates in more complex paths' - ); + 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() { - shouldThrow( - function() { - CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}'); - }, + expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:13' ); - shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar'); - shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar'); + expectTemplate('{{[this]}}') + .withInput({ this: 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{text/[this]}}') + .withInput({ text: { this: 'bar' } }) + .toCompileTo('bar'); }); it('this keyword in helpers', function() { @@ -501,108 +492,105 @@ describe('basic context', function() { 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' - ); + 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() { - var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; - shouldThrow( - function() { - CompilerContext.compile(string); - }, + expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:17' ); - shouldCompileTo( - '{{foo [this]}}', - { + expectTemplate('{{foo [this]}}') + .withInput({ foo: function(value) { return value; }, this: 'bar' - }, - 'bar' - ); - shouldCompileTo( - '{{foo text/[this]}}', - { + }) + .toCompileTo('bar'); + + expectTemplate('{{foo text/[this]}}') + .withInput({ foo: function(value) { return value; }, text: { this: 'bar' } - }, - 'bar' - ); + }) + .toCompileTo('bar'); }); it('pass string literals', function() { - shouldCompileTo('{{"foo"}}', {}, ''); - shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar'); - shouldCompileTo( - '{{#"foo"}}{{.}}{{/"foo"}}', - { foo: ['bar', 'baz'] }, - 'barbaz' - ); + expectTemplate('{{"foo"}}').toCompileTo(''); + + expectTemplate('{{"foo"}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') + .withInput({ + foo: ['bar', 'baz'] + }) + .toCompileTo('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}}', - { + 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; } - }, - 'bar1' - ); + }) + .toCompileTo('bar1'); }); it('pass boolean literals', function() { - shouldCompileTo('{{true}}', {}, ''); - shouldCompileTo('{{true}}', { '': 'foo' }, ''); - shouldCompileTo('{{false}}', { false: 'foo' }, 'foo'); + 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) { + expectTemplate('{{foo (false)}}') + .withInput({ + false: function() { + return 'bar'; + } + }) + .withHelper('foo', function(arg) { return arg; - } - }; - shouldCompileTo( - '{{foo (false)}}', - [ - { - false: function() { - return 'bar'; - } - }, - helpers - ], - 'bar' - ); + }) + .toCompileTo('bar'); }); }); diff --git a/spec/blocks.js b/spec/blocks.js index ad534aaba..f15655428 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -1,455 +1,405 @@ describe('blocks', function() { it('array', function() { var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; - shouldCompileTo( - string, - hash, - 'goodbye! Goodbye! GOODBYE! cruel world!', - 'Arrays iterate over the contents when not empty' - ); - - shouldCompileTo( - string, - { goodbyes: [], world: 'world' }, - 'cruel world!', - 'Arrays ignore the contents when empty' - ); + + expectTemplate(string) + .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!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage('Arrays ignore the contents when empty') + .toCompileTo('cruel world!'); }); it('array without data', function() { - var string = - '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'; - var hash = { - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], - world: 'world' - }; - shouldCompileTo( - string, - [hash, , , false], - 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE' - ); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'The @index variable is used' - ); + 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' - }; - shouldCompileTo( - string, - hash, - 'cruel world!', - 'Arrays iterate over the contents when not empty' - ); - - shouldCompileTo( - string, - { goodbyes: [], world: 'world' }, - 'cruel world!', - 'Arrays ignore the contents when empty' - ); + + expectTemplate(string) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('Arrays iterate over the contents when not empty') + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage('Arrays ignore the contents when empty') + .toCompileTo('cruel world!'); }); it('block with complex lookup', function() { - var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}'; - var hash = { - name: 'Alan', - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }] - }; - - shouldCompileTo( - string, - hash, - 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ', - 'Templates can access variables in contexts up the stack with relative path syntax' - ); + 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' + ) + .toCompileTo( + 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ' + ); }); it('multiple blocks with complex lookup', function() { - var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}'; - var hash = { - name: 'Alan', - goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }] - }; - - shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan'); + 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}}'; - - shouldThrow(function() { - CompilerContext.compile(string); - }, 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' }] }] - }; - - shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!'); + 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 template = CompilerContext.compile( - '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', - { data: false } - ); - - var result = template({ - person: [ - { first: 'Alan', last: 'Johnson' }, - { first: 'Alan', last: 'Johnson' } - ] - }); - equals(result, 'Alan JohnsonAlan Johnson'); + 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 = {}; - shouldCompileTo( - string, - hash, - 'Right On!', - "Inverted section rendered when value isn't set." - ); + 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 }; - shouldCompileTo( - string, - hash, - 'Right On!', - 'Inverted section rendered when value is false.' - ); + 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: [] }; - shouldCompileTo( - string, - hash, - 'Right On!', - 'Inverted section rendered when value is empty set.' - ); + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withInput({ goodbyes: [] }) + .withMessage('Inverted section rendered when value is empty set.') + .toCompileTo('Right On!'); }); it('block inverted sections', function() { - shouldCompileTo( - '{{#people}}{{name}}{{^}}{{none}}{{/people}}', - { none: 'No people' }, - 'No people' - ); + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('No people'); }); + it('chained inverted sections', function() { - shouldCompileTo( - '{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', - { none: 'No people' }, - 'No people' - ); - shouldCompileTo( - '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', - { none: 'No people' }, - 'No people' - ); - shouldCompileTo( - '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', - { none: 'No people' }, - 'No people' - ); + 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() { - shouldThrow(function() { - shouldCompileTo( - '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', - { none: 'No people' }, - 'No people' - ); - }, Error); + expectTemplate( + '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' + ).toThrow(Error); }); it('block inverted sections with empty arrays', function() { - shouldCompileTo( - '{{#people}}{{name}}{{^}}{{none}}{{/people}}', - { none: 'No people', people: [] }, - 'No people' - ); + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ + none: 'No people', + people: [] + }) + .toCompileTo('No people'); }); }); describe('standalone sections', function() { it('block standalone else sections', function() { - shouldCompileTo( - '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); - shouldCompileTo( - '{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', - { none: 'No people' }, - 'No people\n' - ); - shouldCompileTo( - '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); + 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() { - shouldCompileTo( - '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', - [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }], - '\nNo people\n\n' - ); - shouldCompileTo( - '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n', - [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }], - '\nNo people\n\n' - ); + 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() { - shouldCompileTo( - '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); - shouldCompileTo( - '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); + 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() { - shouldCompileTo( - '{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', - { data: [1, 3, 5] }, - '1\n3\n5\nOK.' - ); + expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') + .withInput({ + data: [1, 3, 5] + }) + .toCompileTo('1\n3\n5\nOK.'); }); }); 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' }] }] }; - - shouldCompileTo( - string, - [hash, undefined, undefined, true], - 'Goodbye cruel OMG!' - ); + 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' }] }] - }; - - shouldCompileTo( - string, - [hash, undefined, undefined, true], - 'Goodbye cruel OMG!' - ); + 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' }] }] - }; - - shouldCompileTo( - string, - [hash, undefined, undefined, true], - 'Goodbye cruel ' - ); + 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 '); }); }); 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; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .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'; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator}}suc{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); it('should apply block decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#*decorator}}success{{/decorator}}{{/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.fn(); return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); + it('should support nested decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' + ) + .withHelper('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(); - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .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) { + expectTemplate( + '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/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 = (fn.run || '') + options.fn(); return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .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; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator foo}}{{/helper}}', - { hash: { foo: 'success' }, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .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; - } - }; - shouldCompileTo( - '{{*decorator "success"}}', - { hash: { foo: 'success' }, decorators: 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; - } - }; - shouldCompileTo( - '{{*decorator foo}}', - { hash: { foo: 'fail' }, decorators: decorators }, - '' - ); + }) + .withInput({ foo: 'fail' }) + .toCompileTo(''); equals(run, true); }); @@ -481,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 c74092e1b..a43fb81f5 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -2,157 +2,184 @@ describe('builtin helpers', function() { describe('#if', function() { it('if', function() { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; - shouldCompileTo( - string, - { goodbye: true, world: 'world' }, - 'GOODBYE cruel world!', - 'if with boolean argument shows the contents when true' - ); - shouldCompileTo( - string, - { goodbye: 'dummy', world: 'world' }, - 'GOODBYE cruel world!', - 'if with string argument shows the contents' - ); - shouldCompileTo( - string, - { goodbye: false, world: 'world' }, - 'cruel world!', - 'if with boolean argument does not show the contents when false' - ); - shouldCompileTo( - string, - { world: 'world' }, - 'cruel world!', - 'if with undefined does not show the contents' - ); - shouldCompileTo( - string, - { goodbye: ['foo'], world: 'world' }, - 'GOODBYE cruel world!', - 'if with non-empty array shows the contents' - ); - shouldCompileTo( - string, - { goodbye: [], world: 'world' }, - 'cruel world!', - 'if with empty array does not show the contents' - ); - shouldCompileTo( - string, - { goodbye: 0, world: 'world' }, - 'cruel world!', - 'if with zero does not show the contents' - ); - shouldCompileTo( - '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!', - { goodbye: 0, world: 'world' }, - 'GOODBYE cruel world!', - 'if with zero does not show the contents' - ); + + expectTemplate(string) + .withInput({ + goodbye: true, + world: 'world' + }) + .withMessage('if with boolean argument shows the contents when true') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: 'dummy', + world: 'world' + }) + .withMessage('if with string argument shows the contents') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: false, + world: 'world' + }) + .withMessage( + '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'], + world: 'world' + }) + .withMessage('if with non-empty array shows the contents') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: [], + world: 'world' + }) + .withMessage('if with empty array does not show the contents') + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: 0, + world: 'world' + }) + .withMessage('if with zero does not show the contents') + .toCompileTo('cruel world!'); + + expectTemplate( + '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!' + ) + .withInput({ + goodbye: 0, + world: 'world' + }) + .withMessage('if with zero does not show the contents') + .toCompileTo('GOODBYE cruel world!'); }); it('if with function argument', function() { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; - shouldCompileTo( - string, - { + + expectTemplate(string) + .withInput({ goodbye: function() { return true; }, world: 'world' - }, - 'GOODBYE cruel world!', - 'if with function shows the contents when function returns true' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function shows the contents when function returns true' + ) + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return this.world; }, world: 'world' - }, - 'GOODBYE cruel world!', - 'if with function shows the contents when function returns string' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function shows the contents when function returns string' + ) + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return false; }, world: 'world' - }, - 'cruel world!', - 'if with function does not show the contents when returns false' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function does not show the contents when returns false' + ) + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return this.foo; }, world: 'world' - }, - 'cruel world!', - 'if with function does not show the contents when returns undefined' - ); + }) + .withMessage( + 'if with function does not show the contents when returns undefined' + ) + .toCompileTo('cruel world!'); }); it('should not change the depth list', function() { - var string = - '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; - shouldCompileTo( - string, - { foo: { goodbye: true }, world: 'world' }, - 'GOODBYE cruel world!' - ); + expectTemplate( + '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' + ) + .withInput({ + foo: { goodbye: true }, + world: 'world' + }) + .toCompileTo('GOODBYE cruel world!'); }); }); describe('#with', function() { it('with', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - shouldCompileTo( - string, - { person: { first: 'Alan', last: 'Johnson' } }, - 'Alan Johnson' - ); + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') + .withInput({ + person: { + first: 'Alan', + last: 'Johnson' + } + }) + .toCompileTo('Alan Johnson'); }); + it('with with function argument', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - shouldCompileTo( - string, - { + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') + .withInput({ person: function() { - return { first: 'Alan', last: 'Johnson' }; + return { + first: 'Alan', + last: 'Johnson' + }; } - }, - 'Alan Johnson' - ); + }) + .toCompileTo('Alan Johnson'); }); + it('with with else', function() { - var string = - '{{#with person}}Person is present{{else}}Person is not present{{/with}}'; - shouldCompileTo(string, {}, '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}}'; - shouldCompileTo( - string, - { person: { first: 'Alan', last: 'Johnson' } }, - 'Alan Johnson' - ); + expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') + .withInput({ + person: { + first: 'Alan', + last: 'Johnson' + } + }) + .toCompileTo('Alan Johnson'); }); - it('works when data is disabled', function() { - var template = CompilerContext.compile( - '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', - { data: false } - ); - var result = template({ person: { first: 'Alan', last: 'Johnson' } }); - equals(result, 'Alan Johnson'); + it('works when data is disabled', function() { + expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') + .withInput({ person: { first: 'Alan', last: 'Johnson' } }) + .withCompileOptions({ data: false }) + .toCompileTo('Alan Johnson'); }); }); @@ -165,55 +192,55 @@ 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' - }; - shouldCompileTo( - string, - hash, - 'goodbye! Goodbye! GOODBYE! cruel world!', - 'each with array argument iterates over the contents when not empty' - ); - shouldCompileTo( - string, - { goodbyes: [], world: 'world' }, - 'cruel world!', - 'each with array argument ignores the contents when empty' - ); + + expectTemplate(string) + .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: [], + world: 'world' + }) + .withMessage('each with array argument ignores the contents when empty') + .toCompileTo('cruel world!'); }); it('each without data', function() { - var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - shouldCompileTo( - string, - [hash, , , , false], - 'goodbye! Goodbye! GOODBYE! cruel world!' - ); + 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' }; - shouldCompileTo( - '{{#each .}}{{.}}{{/each}}', - [hash, , , , false], - 'cruelworld' - ); + expectTemplate('{{#each .}}{{.}}{{/each}}') + .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}}!'; - shouldCompileTo(string, [, , , ,], 'cruel !'); + expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') + .withInput(undefined) + .toCompileTo('cruel !'); }); it('each with an object and @key', function() { @@ -241,269 +268,232 @@ describe('builtin helpers', function() { true, 'each with object argument iterates over the contents when not empty' ); - shouldCompileTo(string, { goodbyes: {}, world: 'world' }, 'cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: {}, + world: 'world' + }) + .toCompileTo('cruel world!'); }); 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'The @index variable is used' - ); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!', - 'The @index variable is used' - ); + 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!' + ); }); 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' - ); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'The @index variable is used' - ); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'goodbye! cruel world!', 'The @first variable is used'); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', - 'The @first variable is used' - ); + 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!' + ); }); 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'goodbye! cruel world!', 'The @first variable is used'); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'GOODBYE! cruel world!', 'The @last variable is used'); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'Goodbye! cruel world!', 'The @last variable is used'); + 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', - 'The @last variable is used' - ); - }); - - it('each with function argument', function() { - var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: function() { - return [ + 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' - }; - shouldCompileTo( - string, - hash, - 'goodbye! Goodbye! GOODBYE! cruel world!', - 'each with array function argument iterates over the contents when not empty' - ); - shouldCompileTo( - string, - { goodbyes: [], world: 'world' }, - 'cruel world!', - 'each with array function argument ignores the contents when empty' - ); + ], + world: 'world' + }) + .withMessage('The @last variable is used') + .toCompileTo( + '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!' + ); }); - 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' - }; + it('each with function argument', function() { + var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var template = CompilerContext.compile(string); - var result = template(hash); + expectTemplate(string) + .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!'); - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'Empty string key is not skipped' - ); + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage( + 'each with array function argument ignores the contents when empty' + ) + .toCompileTo('cruel world!'); }); - it('data passed to helpers', function() { - var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'; - var hash = { letters: ['a', 'b', 'c'] }; + it('each object when last key is an empty string', function() { + 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!'); + }); - var template = CompilerContext.compile(string); - var result = template(hash, { - data: { - exclaim: '!' - } - }); - equal(result, 'a!b!c!', 'should output data'); + it('data passed to helpers', function() { + expectTemplate( + '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' + ) + .withInput({ letters: ['a', 'b', 'c'] }) + .withMessage('should output data') + .withRuntimeOptions({ + data: { + exclaim: '!' + } + }) + .toCompileTo('a!b!c!'); }); it('each on implicit context', function() { - shouldThrow( - function() { - var template = CompilerContext.compile( - '{{#each}}{{text}}! {{/each}}cruel world!' - ); - template({}); - }, + expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( handlebarsEnv.Exception, 'Must pass iterator to #each' ); @@ -530,25 +520,30 @@ describe('builtin helpers', function() { return new Iterator(this.arr); }; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var goodbyes = new Iterable([ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ]); - var goodbyesEmpty = new Iterable([]); - var hash = { goodbyes: goodbyes, world: 'world' }; - shouldCompileTo( - string, - hash, - 'goodbye! Goodbye! GOODBYE! cruel world!', - 'each with array argument iterates over the contents when not empty' - ); - shouldCompileTo( - string, - { goodbyes: goodbyesEmpty, world: 'world' }, - 'cruel world!', - 'each with array argument ignores the contents when empty' - ); + + expectTemplate(string) + .withInput({ + goodbyes: new Iterable([ + { 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: new Iterable([]), + world: 'world' + }) + .withMessage( + 'each with array argument ignores the contents when empty' + ) + .toCompileTo('cruel world!'); }); } }); @@ -572,36 +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; }; - shouldCompileTo(string, hash, '', 'log should not display'); + 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; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + 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) { @@ -617,12 +613,13 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, 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) { @@ -631,13 +628,16 @@ describe('builtin helpers', function() { console.error = $error; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + 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) { @@ -646,13 +646,15 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: '03' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); }); it('should handle string log levels', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -660,17 +662,24 @@ describe('builtin helpers', function() { called = true; }; - shouldCompileTo(string, [hash, , , , { level: 'error' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: 'error' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); called = false; - shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: 'ERROR' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); }); + it('should handle hash log levels', function() { - var string = '{{log blah level="error"}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -678,12 +687,13 @@ describe('builtin helpers', function() { called = true; }; - shouldCompileTo(string, hash, ''); + expectTemplate('{{log blah level="error"}}') + .withInput({ blah: 'whee' }) + .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() { @@ -691,12 +701,13 @@ describe('builtin helpers', function() { console.info = console.log = console.error = console.debug = $log; }; - shouldCompileTo(string, 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) { @@ -707,13 +718,13 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, 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() { @@ -722,8 +733,8 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); expect(called).to.be.true(); }); @@ -732,22 +743,15 @@ 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'] }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'foobar'); + 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'] }; - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, ''); + 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 0defdc14a..bde617326 100644 --- a/spec/data.js +++ b/spec/data.js @@ -1,31 +1,24 @@ describe('data', function() { it('passing in data to a compiled function that expects data - works with helpers', function() { - var template = CompilerContext.compile('{{hello}}', { data: true }); - - var helpers = { - hello: function(options) { + expectTemplate('{{hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.data.adjective + ' ' + this.noun; - } - }; - - var result = template( - { noun: 'cat' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('happy cat', result, 'Data output by helper'); + }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withInput({ noun: 'cat' }) + .withMessage('Data output by helper') + .toCompileTo('happy cat'); }); it('data can be looked up via @foo', function() { - var template = CompilerContext.compile('{{@hello}}'); - var result = template({}, { data: { hello: 'hello' } }); - equals('hello', result, '@foo retrieves template data'); + expectTemplate('{{@hello}}') + .withRuntimeOptions({ data: { hello: 'hello' } }) + .withMessage('@foo retrieves template data') + .toCompileTo('hello'); }); it('deep @foo triggers automatic top-level data', function() { - var template = CompilerContext.compile( - '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}' - ); - var helpers = Handlebars.createFrame(handlebarsEnv.helpers); helpers.let = function(options) { @@ -39,124 +32,92 @@ describe('data', function() { return options.fn(this, { data: frame }); }; - var result = template({ foo: true }, { helpers: helpers }); - equals('Hello world', result, 'Automatic data was triggered'); + expectTemplate( + '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}' + ) + .withInput({ foo: true }) + .withHelpers(helpers) + .withMessage('Automatic data was triggered') + .toCompileTo('Hello world'); }); it('parameter data can be looked up via @foo', function() { - var template = CompilerContext.compile('{{hello @world}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world}}') + .withRuntimeOptions({ data: { world: 'world' } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: 'world' } }); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('hash values can be looked up via @foo', function() { - var template = CompilerContext.compile('{{hello noun=@world}}'); - var helpers = { - hello: function(options) { + expectTemplate('{{hello noun=@world}}') + .withRuntimeOptions({ data: { world: 'world' } }) + .withHelper('hello', function(options) { return 'Hello ' + options.hash.noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: 'world' } }); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('nested parameter data can be looked up via @foo.bar', function() { - var template = CompilerContext.compile('{{hello @world.bar}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world.bar}}') + .withRuntimeOptions({ data: { world: { bar: 'world' } } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template( - {}, - { helpers: helpers, data: { world: { bar: 'world' } } } - ); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('nested parameter data does not fail with @world.bar', function() { - var template = CompilerContext.compile('{{hello @world.bar}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world.bar}}') + .withRuntimeOptions({ data: { foo: { bar: 'world' } } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template( - {}, - { helpers: helpers, data: { foo: { bar: 'world' } } } - ); - equals( - 'Hello undefined', - result, - '@foo as a parameter retrieves template data' - ); + }) + .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}}'; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error); + expectTemplate( + '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' + ).toThrow(Error); }); it('data can be functions', function() { - var template = CompilerContext.compile('{{@hello}}'); - var result = template( - {}, - { + expectTemplate('{{@hello}}') + .withRuntimeOptions({ data: { hello: function() { return 'hello'; } } - } - ); - equals('hello', result); + }) + .toCompileTo('hello'); }); + it('data can be functions with params', function() { - var template = CompilerContext.compile('{{@hello "hello"}}'); - var result = template( - {}, - { + expectTemplate('{{@hello "hello"}}') + .withRuntimeOptions({ data: { hello: function(arg) { return arg; } } - } - ); - equals('hello', result); + }) + .toCompileTo('hello'); }); it('data is inherited downstream', function() { - var template = CompilerContext.compile( - '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}', - { 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) { @@ -164,201 +125,154 @@ describe('data', function() { } } return options.fn(this, { data: frame }); - } - }; - - var result = template( - { bar: { baz: 'hello world' } }, - { helpers: helpers, data: {} } - ); - equals('2hello world1', result, 'data variables are inherited downstream'); + }) + .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 template = CompilerContext.compile('{{>myPartial}}', { data: true }); - - var partials = { - myPartial: CompilerContext.compile('{{hello}}', { data: true }) - }; - - var helpers = { - hello: function(options) { + expectTemplate('{{>myPartial}}') + .withCompileOptions({ data: true }) + .withPartial('myPartial', '{{hello}}') + .withHelper('hello', function(options) { return options.data.adjective + ' ' + this.noun; - } - }; - - var result = template( - { noun: 'cat' }, - { helpers: helpers, partials: partials, data: { adjective: 'happy' } } - ); - equals('happy cat', result, 'Data output by helper inside partial'); + }) + .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 template = CompilerContext.compile('{{hello world}}', { 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 result = template( - { exclaim: true, world: 'world' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('happy world!', result, 'Data output by helper'); + }) + .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 template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', { - 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 result = template( - { exclaim: true }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('happy world!', result, 'Data output by helper'); + }) + .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 template = CompilerContext.compile( - '{{#hello}}{{world ../zomg}}{{/hello}}', - { 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 result = template( - { exclaim: true, zomg: 'world' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('happy world?', result, 'Data output by helper'); + }) + .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 template = CompilerContext.compile( - '{{#hello}}{{world ../zomg}}{{/hello}}', - { 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 result = template( - { exclaim: true, zomg: 'world' }, - { helpers: helpers, data: { adjective: 'happy', accessData: '#win' } } - ); - equals('#win happy world?', result, 'Data output by helper'); + }) + .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 template = CompilerContext.compile( - '{{#hello}}{{world zomg}}{{/hello}}', - { 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 result = template( - { exclaim: true, zomg: 'planet' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('sad world?', result, 'Overriden data output by helper'); + }) + .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 template = CompilerContext.compile( - '{{#hello}}{{world ../zomg}}{{/hello}}', - { 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 result = template( - { exclaim: true, zomg: 'world' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('sad world?', result, 'Overriden data output by helper'); + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Overriden data output by helper') + .toCompileTo('sad world?'); }); describe('@root', function() { it('the root context can be looked up via @root', function() { - var template = CompilerContext.compile('{{@root.foo}}'); - var result = template({ foo: 'hello' }, { data: {} }); - equals('hello', result); - - result = template({ foo: 'hello' }, {}); - equals('hello', result); + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'hello' }) + .withRuntimeOptions({ data: {} }) + .toCompileTo('hello'); + + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'hello' }) + .toCompileTo('hello'); }); + it('passed root values take priority', function() { - var template = CompilerContext.compile('{{@root.foo}}'); - var result = template({}, { data: { root: { foo: 'hello' } } }); - equals('hello', result); + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'should not be used' }) + .withRuntimeOptions({ data: { root: { foo: 'hello' } } }) + .toCompileTo('hello'); }); }); describe('nesting', function() { it('the root context can be looked up via @root', function() { - var template = CompilerContext.compile( + expectTemplate( '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' - ); - var result = template( - { foo: 'hello' }, - { - helpers: { - helper: function(options) { - var frame = Handlebars.createFrame(options.data); - frame.depth = options.data.depth + 1; - return options.fn(this, { data: frame }); - } - }, + ) + .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 }); + }) + .withRuntimeOptions({ data: { depth: 0 } - } - ); - equals('2 1 0', result); + }) + .toCompileTo('2 1 0'); }); }); }); diff --git a/spec/env/common.js b/spec/env/common.js index 0ecebcf45..a122f4d6e 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -129,6 +129,7 @@ function HandlebarsTestBench(templateAsString) { this.templateAsString = templateAsString; this.helpers = {}; this.partials = {}; + this.decorators = {}; this.input = {}; this.message = 'Template' + templateAsString + ' does not evaluate to expected output'; @@ -146,11 +147,43 @@ HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) { return this; }; +HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) { + var self = this; + Object.keys(helperFunctions).forEach(function(name) { + self.withHelper(name, helperFunctions[name]); + }); + return this; +}; + HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) { this.partials[name] = partialAsString; return this; }; +HandlebarsTestBench.prototype.withPartials = function(partials) { + var self = this; + Object.keys(partials).forEach(function(name) { + self.withPartial(name, partials[name]); + }); + return this; +}; + +HandlebarsTestBench.prototype.withDecorator = function( + name, + decoratorFunction +) { + this.decorators[name] = decoratorFunction; + return this; +}; + +HandlebarsTestBench.prototype.withDecorators = function(decorators) { + var self = this; + Object.keys(decorators).forEach(function(name) { + self.withDecorator(name, decorators[name]); + }); + return this; +}; + HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) { this.compileOptions = compileOptions; return this; @@ -167,19 +200,18 @@ HandlebarsTestBench.prototype.withMessage = function(message) { }; HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { - expect(this._compileAndExecute()).to.equal(expectedOutputAsString); + expect(this._compileAndExecute()).to.equal( + expectedOutputAsString, + this.message + ); }; // see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) -HandlebarsTestBench.prototype.toThrow = function( - errorLike, - errMsgMatcher, - msg -) { +HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) { var self = this; expect(function() { self._compileAndExecute(); - }).to.throw(errorLike, errMsgMatcher, msg); + }).to.throw(errorLike, errMsgMatcher, this.message); }; HandlebarsTestBench.prototype._compileAndExecute = function() { @@ -202,5 +234,6 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function() { }); combinedRuntimeOptions.helpers = this.helpers; combinedRuntimeOptions.partials = this.partials; + combinedRuntimeOptions.decorators = this.decorators; return combinedRuntimeOptions; }; diff --git a/spec/helpers.js b/spec/helpers.js index 60140a096..5166d58d6 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -1,64 +1,45 @@ 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 + '' ); - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye' - ); + }) + .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(); - } - }; - shouldCompileTo( - string, - [hash, helpers], - ' {{test}} ', - 'raw block helper gets raw content' - ); + }) + .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; - } - }; - shouldCompileTo( - string, - [hash, helpers], - ' {{test}} 123', - 'raw block helper gets raw content' - ); + }) + .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) { + expectTemplate(template) + .withHelper('identity', function(options) { return options.fn(); - } - }; - shouldCompileTo(template, [{}, helpers], expected); + }) + .toCompileTo(expected); } it('helper for nested raw block gets raw content', function() { @@ -92,60 +73,47 @@ describe('helpers', function() { it('helper for nested raw block throw exception when with missing closing braces', function() { var string = '{{{{a}}}} {{{{/a'; - shouldThrow(function() { - Handlebars.compile(string)(); - }); + expectTemplate(string).toThrow(); }); }); 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; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ' - ); + }) + .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; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ' - ); + }) + .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' - ); + }) + .toCompileTo('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' - ); + }) + .toCompileTo('Goodbye'); }); + it('helper returning undefined value', function() { - shouldCompileTo(' {{nothere}}', [{}, { nothere: function() {} }], ' '); - shouldCompileTo( - ' {{#nothere}}{{/nothere}}', - [{}, { nothere: function() {} }], - ' ' - ); + expectTemplate(' {{nothere}}') + .withHelpers({ + nothere: function() {} + }) + .toCompileTo(' '); + + expectTemplate(' {{#nothere}}{{/nothere}}') + .withHelpers({ + nothere: function() {} + }) + .toCompileTo(' '); }); it('block helper', function() { - var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; - var template = CompilerContext.compile(string); - - var result = template( - { world: 'world' }, - { - helpers: { - goodbyes: function(options) { - return options.fn({ text: 'GOODBYE' }); - } - } - } - ); - equal(result, 'GOODBYE! cruel world!', 'Block helper executed'); + expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!') + .withInput({ world: 'world' }) + .withHelper('goodbyes', function(options) { + return options.fn({ text: 'GOODBYE' }); + }) + .withMessage('Block helper executed') + .toCompileTo('GOODBYE! cruel world!'); }); it('block helper staying in the same context', function() { - var string = '{{#form}}
{{name}}
{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { name: 'Yehuda' }, - { - helpers: { - form: function(options) { - return ''; - } - } - } - ); - equal( - result, - '', - 'Block helper executed with current context' - ); + expectTemplate('{{#form}}{{name}}
{{/form}}') + .withInput({ name: 'Yehuda' }) + .withHelper('form', function(options) { + return ''; + }) + .withMessage('Block helper executed with current context') + .toCompileTo(''); }); it('block helper should have context in this', function() { - var source = - '{{name}}
{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { yehuda: { name: 'Yehuda' } }, - { - helpers: { - form: function(context, options) { - return ''; - } - } - } - ); - equal(result, '', 'Context variable resolved'); + expectTemplate('{{#form yehuda}}{{name}}
{{/form}}') + .withInput({ yehuda: { name: 'Yehuda' } }) + .withHelper('form', function(context, options) { + return ''; + }) + .withMessage('Context variable resolved') + .toCompileTo(''); }); it('block helper passing a complex path context', function() { - var string = '{{#form yehuda/cat}}{{name}}
{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }, - { - helpers: { - form: function(context, options) { - return ''; - } - } - } - ); - equal( - result, - '', - 'Complex path variable resolved' - ); + expectTemplate('{{#form yehuda/cat}}{{name}}
{{/form}}') + .withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }) + .withHelper('form', function(context, options) { + return ''; + }) + .withMessage('Complex path variable resolved') + .toCompileTo(''); }); it('nested block helpers', function() { - var string = - '{{#form yehuda}}{{name}}
{{#link}}Hello{{/link}}{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { + expectTemplate( + '{{#form yehuda}}{{name}}
{{#link}}Hello{{/link}}{{/form}}' + ) + .withInput({ yehuda: { name: 'Yehuda' } - }, - { - helpers: { - link: function(options) { - return '' + options.fn(this) + ''; - }, - form: function(context, options) { - return ''; - } - } - } - ); - equal( - result, - '', - 'Both blocks executed' - ); + }) + .withHelper('link', function(options) { + return '' + options.fn(this) + ''; + }) + .withHelper('form', function(context, options) { + return ''; + }) + .withMessage('Both blocks executed') + .toCompileTo(''); }); it('block helper inverted sections', function() { @@ -345,35 +261,28 @@ 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 - shouldCompileTo( - string, - [hash, { list: list }], - 'Nobody's here
", - 'an inverse wrapper can be optionally called' - ); - shouldCompileTo( - messageString, - [rootMessage, { list: list }], - 'Nobody's here
', - 'the context of an inverse is the parent of the block' - ); + expectTemplate(string) + .withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] }) + .withHelpers({ list: list }) + .withMessage('an inverse wrapper is passed in as a new context') + .toCompileTo('Nobody's here
"); + + 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
'); }); it('pathed lambas with parameters', function() { @@ -388,84 +297,73 @@ describe('helpers', function() { return 'fail'; } }; - shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning'); - shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning'); + + expectTemplate('{{./helper 1}}') + .withInput(hash) + .withHelpers(helpers) + .toCompileTo('winning'); + + expectTemplate('{{hash/helper 1}}') + .withInput(hash) + .withHelpers(helpers) + .toCompileTo('winning'); }); describe('helpers hash', function() { it('providing a helpers hash', function() { - shouldCompileTo( - 'Goodbye {{cruel}} {{world}}!', - [ - { cruel: 'cruel' }, - { - world: function() { - return 'world'; - } + expectTemplate('Goodbye {{cruel}} {{world}}!') + .withInput({ cruel: 'cruel' }) + .withHelpers({ + world: function() { + return 'world'; } - ], - 'Goodbye cruel world!', - 'helpers hash is available' - ); - - shouldCompileTo( - 'Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!', - [ - { iter: [{ cruel: 'cruel' }] }, - { - world: function() { - return 'world'; - } + }) + .withMessage('helpers hash is available') + .toCompileTo('Goodbye cruel world!'); + + expectTemplate('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!') + .withInput({ iter: [{ cruel: 'cruel' }] }) + .withHelpers({ + world: function() { + return 'world'; } - ], - 'Goodbye cruel world!', - 'helpers hash is available inside other blocks' - ); + }) + .withMessage('helpers hash is available inside other blocks') + .toCompileTo('Goodbye cruel world!'); }); it('in cases of conflict, helpers win', function() { - shouldCompileTo( - '{{{lookup}}}', - [ - { lookup: 'Explicit' }, - { - lookup: function() { - return 'helpers'; - } + expectTemplate('{{{lookup}}}') + .withInput({ lookup: 'Explicit' }) + .withHelpers({ + lookup: function() { + return 'helpers'; } - ], - 'helpers', - 'helpers hash has precedence escaped expansion' - ); - shouldCompileTo( - '{{lookup}}', - [ - { lookup: 'Explicit' }, - { - lookup: function() { - return 'helpers'; - } + }) + .withMessage('helpers hash has precedence escaped expansion') + .toCompileTo('helpers'); + + expectTemplate('{{lookup}}') + .withInput({ lookup: 'Explicit' }) + .withHelpers({ + lookup: function() { + return 'helpers'; } - ], - 'helpers', - 'helpers hash has precedence simple expansion' - ); + }) + .withMessage('helpers hash has precedence simple expansion') + .toCompileTo('helpers'); }); it('the helpers hash is available is nested contexts', function() { - shouldCompileTo( - '{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}', - [ - { outer: { inner: { unused: [] } } }, - { - helper: function() { - return 'helper'; - } + expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}') + .withInput({ outer: { inner: { unused: [] } } }) + .withHelpers({ + helper: function() { + return 'helper'; } - ], - 'helper', - 'helpers hash is available in nested contexts.' - ); + }) + .withMessage('helpers hash is available in nested contexts.') + .toCompileTo('helper'); }); it('the helper hash should augment the global hash', function() { @@ -473,18 +371,16 @@ describe('helpers', function() { return 'found it!'; }); - shouldCompileTo( - '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}', - [ - { cruel: 'cruel' }, - { - world: function() { - return 'world!'; - } + expectTemplate( + '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' + ) + .withInput({ cruel: 'cruel' }) + .withHelpers({ + world: function() { + return 'world!'; } - ], - 'found it! Goodbye cruel world!!' - ); + }) + .toCompileTo('found it! Goodbye cruel world!!'); }); }); @@ -513,12 +409,13 @@ describe('helpers', function() { } }); - shouldCompileTo( - '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}', - [{ cruel: 'cruel' }], - 'found it! Goodbye cruel world!!' - ); + expectTemplate( + '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' + ) + .withInput({ cruel: 'cruel' }) + .toCompileTo('found it! Goodbye cruel world!!'); }); + it('fails with multiple and args', function() { shouldThrow( function() { @@ -541,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'; } @@ -551,39 +447,27 @@ describe('helpers', function() { times2 = 'NaN'; } return 'Hello ' + times + ' ' + times2 + ' times'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -1.2 1.2 times', - 'template with a negative integer literal' - ); + }) + .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'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -12 times', - 'template with a negative integer literal' - ); + }) + .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'; } @@ -596,115 +480,74 @@ describe('helpers', function() { return ( 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 ); - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello world 12 times: true false', - 'template with a simple String literal' - ); + }) + .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"}}'; - shouldThrow(function() { - CompilerContext.compile(string); - }, 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; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello "world"', - 'template with an escaped String literal' - ); + }) + .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; - } - }; - shouldCompileTo( - string, - [{}, helpers], - "Message: Hello Alan's world", - "template with a ' mark" - ); + }) + .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'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -12 times', - 'template with a negative integer literal' - ); + }) + .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; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Message: Goodbye cruel world', - 'regular helpers with multiple params' - ); + }) + .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 }); - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Message: Goodbye cruel world', - 'block helpers with multiple params' - ); + }) + .withMessage('block helpers with multiple params') + .toCompileTo('Message: Goodbye cruel world'); }); }); describe('hash', function() { it('helpers can take an optional hash', function() { - var template = CompilerContext.compile( - '{{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 + @@ -714,50 +557,36 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var context = {}; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL WORLD 12 TIMES', 'Helper output hash'); + }) + .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 = {}; + } - var template = CompilerContext.compile( - '{{goodbye cruel="CRUEL" world="WORLD" print=true}}' - ); - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL WORLD', 'Helper output hash'); + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}') + .withHelper('goodbye', goodbye) + .withMessage('Helper output hash') + .toCompileTo('GOODBYE CRUEL WORLD'); - template = CompilerContext.compile( - '{{goodbye cruel="CRUEL" world="WORLD" print=false}}' - ); - result = template(context, { helpers: helpers }); - equals(result, 'NOT PRINTING', 'Boolean helper parameter honored'); + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=false}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean helper parameter honored') + .toCompileTo('NOT PRINTING'); }); it('block helpers can take an optional hash', function() { - var template = CompilerContext.compile( - '{{#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 + @@ -767,20 +596,14 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output'); + }) + .withMessage('Hash parameters output') + .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); it('block helpers can take an optional hash with single quoted stings', function() { - var template = CompilerContext.compile( - '{{#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 + @@ -790,200 +613,173 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output'); + }) + .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'; } - }; + } - var template = CompilerContext.compile( - '{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}' - ); - var result = template({}, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL world', 'Boolean hash parameter honored'); + expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean hash parameter honored') + .toCompileTo('GOODBYE CRUEL world'); - template = CompilerContext.compile( - '{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}' - ); - result = template({}, { helpers: helpers }); - equals(result, 'NOT PRINTING', 'Boolean hash parameter honored'); + expectTemplate('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean hash parameter honored') + .toCompileTo('NOT PRINTING'); }); }); describe('helperMissing', function() { it('if a context is not found, helperMissing is used', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{hello}} {{link_to world}}'); - template({}); - }, - undefined, + expectTemplate('{{hello}} {{link_to world}}').toThrow( /Missing helper: "link_to"/ ); }); 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 + ''); } - } - }; - - shouldCompileTo(string, [context, helpers], 'Hello world'); + }) + .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'); } - } - }; - - shouldCompileTo(string, [context, helpers], 'Hello winning'); + }) + .toCompileTo('Hello winning'); }); }); describe('knownHelpers', function() { it('Known helper should render helper', function() { - var template = CompilerContext.compile('{{hello}}', { - knownHelpers: { hello: true } - }); - - var result = template( - {}, - { - helpers: { - hello: function() { - return 'foo'; - } - } - } - ); - equal(result, 'foo', "'foo' should === '" + result); + expectTemplate('{{hello}}') + .withCompileOptions({ + knownHelpers: { hello: true } + }) + .withHelper('hello', function() { + return 'foo'; + }) + .toCompileTo('foo'); }); it('Unknown helper in knownHelpers only mode should be passed as undefined', function() { - var template = CompilerContext.compile('{{typeof hello}}', { - knownHelpers: { typeof: true }, - knownHelpersOnly: true - }); - - var result = template( - {}, - { - helpers: { - typeof: function(arg) { - return typeof arg; - }, - hello: function() { - return 'foo'; - } - } - } - ); - equal(result, 'undefined', "'undefined' should === '" + result); + expectTemplate('{{typeof hello}}') + .withCompileOptions({ + knownHelpers: { typeof: true }, + knownHelpersOnly: true + }) + .withHelper('typeof', function(arg) { + return typeof arg; + }) + .withHelper('hello', function() { + return 'foo'; + }) + .toCompileTo('undefined'); }); - it('Builtin helpers available in knownHelpers only mode', function() { - var template = CompilerContext.compile('{{#unless foo}}bar{{/unless}}', { - knownHelpersOnly: true - }); - var result = template({}); - equal(result, 'bar', "'bar' should === '" + result); + 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 template = CompilerContext.compile('{{foo}}', { - knownHelpersOnly: true - }); - var result = template({ foo: 'bar' }); - equal(result, 'bar', "'bar' should === '" + result); + it('Field lookup works in knownHelpers only mode', function() { + expectTemplate('{{foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); }); - it('Conditional blocks work in knownHelpers only mode', function() { - var template = CompilerContext.compile('{{#foo}}bar{{/foo}}', { - knownHelpersOnly: true - }); - var result = template({ foo: 'baz' }); - equal(result, 'bar', "'bar' should === '" + result); + it('Conditional blocks work in knownHelpers only mode', function() { + expectTemplate('{{#foo}}bar{{/foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'baz' }) + .toCompileTo('bar'); }); - it('Invert blocks work in knownHelpers only mode', function() { - var template = CompilerContext.compile('{{^foo}}bar{{/foo}}', { - knownHelpersOnly: true - }); - var result = template({ foo: false }); - equal(result, 'bar', "'bar' should === '" + result); + it('Invert blocks work in knownHelpers only mode', function() { + 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 template = CompilerContext.compile('{{foo}}', { - knownHelpersOnly: true - }); - var result = template({ - foo: function() { - return this.bar; - }, - bar: 'bar' - }); - equal(result, 'bar', "'bar' should === '" + result); + 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() { - shouldThrow(function() { - CompilerContext.compile('{{typeof hello}}', { knownHelpersOnly: true }); - }, Error); + expectTemplate('{{typeof hello}}') + .withCompileOptions({ knownHelpersOnly: true }) + .toThrow(Error); }); }); describe('blockHelperMissing', function() { it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() { - var string = '{{#truthy}}yep{{/truthy}}'; - var data = { - truthy: function() { - return true; - } - }; - shouldCompileTo(string, data, 'yep'); + 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; - } - }; - shouldCompileTo(string, 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; @@ -997,212 +793,173 @@ describe('helpers', function() { }; it('should include in ambiguous mustache calls', function() { - shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper'); + expectTemplate('{{helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in helper mustache calls', function() { - shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper'); + expectTemplate('{{helper 1}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in ambiguous block calls', function() { - shouldCompileTo( - '{{#helper}}{{/helper}}', - [context, helpers], - 'ran: helper' - ); + expectTemplate('{{#helper}}{{/helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in simple block calls', function() { - shouldCompileTo( - '{{#./helper}}{{/./helper}}', - [context, helpers], - 'missing: ./helper' - ); + expectTemplate('{{#./helper}}{{/./helper}}') + .withHelpers(helpers) + .toCompileTo('missing: ./helper'); }); + it('should include in helper block calls', function() { - shouldCompileTo( - '{{#helper 1}}{{/helper}}', - [context, helpers], - 'ran: helper' - ); + expectTemplate('{{#helper 1}}{{/helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); - it('should include in known helper calls', function() { - var template = CompilerContext.compile('{{helper}}', { - knownHelpers: { helper: true }, - knownHelpersOnly: true - }); - equal(template({}, { helpers: helpers }), 'ran: helper'); + it('should include in known helper calls', function() { + expectTemplate('{{helper}}') + .withCompileOptions({ + knownHelpers: { helper: true }, + knownHelpersOnly: true + }) + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); it('should include full id', function() { - shouldCompileTo( - '{{#foo.helper}}{{/foo.helper}}', - [{ foo: {} }, helpers], - 'missing: foo.helper' - ); + expectTemplate('{{#foo.helper}}{{/foo.helper}}') + .withInput({ foo: {} }) + .withHelpers(helpers) + .toCompileTo('missing: foo.helper'); }); it('should include full id if a hash is passed', function() { - shouldCompileTo( - '{{#foo.helper bar=baz}}{{/foo.helper}}', - [{ foo: {} }, helpers], - 'helper missing: foo.helper' - ); + expectTemplate('{{#foo.helper bar=baz}}{{/foo.helper}}') + .withInput({ foo: {} }) + .withHelpers(helpers) + .toCompileTo('helper missing: foo.helper'); }); }); describe('name conflicts', function() { it('helpers take precedence over same-named context properties', function() { - var template = CompilerContext.compile('{{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' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD'); }); it('helpers take precedence over same-named context properties$', function() { - var template = CompilerContext.compile( - '{{#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' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD'); }); it('Scoped names take precedence over helpers', function() { - var template = CompilerContext.compile( - '{{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' - }; - - var result = template(context, { helpers: helpers }); - equals( - result, - 'goodbye cruel WORLD cruel GOODBYE', - 'Helper not executed' - ); + }) + .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 template = CompilerContext.compile( + expectTemplate( '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' - ); - - var helpers = { - goodbye: function(options) { + ) + .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' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD goodbye', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD goodbye'); }); }); 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] }); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}', - [hash, helpers], - '1foo' - ); + }) + .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] }); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}', - [hash, helpers], - '1foo' - ); + }) + .toCompileTo('1foo'); }); + it('should not take presedence over pathed values', function() { - var hash = { value: 'bar' }; - var helpers = { - value: function() { + expectTemplate( + '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' + ) + .withInput({ value: 'bar' }) + .withHelper('value', function() { return 'foo'; - }, - goodbyes: function(options) { + }) + .withHelper('goodbyes', function(options) { equals(options.fn.blockParams, 1); return options.fn(this, { blockParams: [1, 2] }); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}', - [hash, helpers], - 'barfoo' - ); + }) + .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' }, { @@ -1210,120 +967,68 @@ describe('helpers', function() { options.fn.blockParams === 1 ? [value++, value++] : undefined } ); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', - [hash, helpers], - '13foo' - ); + }) + .toCompileTo('13foo'); }); it('should allow block params on chained helpers', function() { - var hash = { value: 'foo' }; - var helpers = { - goodbyes: function(options) { + expectTemplate( + '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' + ) + .withInput({ value: 'foo' }) + .withHelper('goodbyes', function(options) { equals(options.fn.blockParams, 1); return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); - } - }; - shouldCompileTo( - '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}', - [hash, helpers], - '1foo' - ); + }) + .toCompileTo('1foo'); }); }); describe('built-in helpers malformed arguments ', function() { it('if helper - too few arguments', function() { - var template = CompilerContext.compile('{{#if}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, string', function() { - var template = CompilerContext.compile('{{#if test "string"}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test "string"}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, undefined', function() { - var template = CompilerContext.compile('{{#if test undefined}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test undefined}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, null', function() { - var template = CompilerContext.compile('{{#if test null}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test null}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('unless helper - too few arguments', function() { - var template = CompilerContext.compile('{{#unless}}{{/unless}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#unless}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); it('unless helper - too many arguments', function() { - var template = CompilerContext.compile( - '{{#unless test null}}{{/unless}}' - ); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#unless test null}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); it('with helper - too few arguments', function() { - var template = CompilerContext.compile('{{#with}}{{/with}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#with}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); it('with helper - too many arguments', function() { - var template = CompilerContext.compile( - '{{#with test "string"}}{{/with}}' - ); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#with test "string"}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index e97cbb083..ed2dc8c56 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -20,14 +20,18 @@ describe('javascript-compiler api', function() { return parent + '.bar_' + name; }; /* eslint-disable camelcase */ - shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food'); + expectTemplate('{{foo}}') + .withInput({ bar_foo: 'food' }) + .toCompileTo('food'); /* eslint-enable camelcase */ }); // Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases // to avoid errors in older browsers. it('should handle reserved words', function() { - shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food'); + expectTemplate('{{foo}} {{~null~}}') + .withInput({ foo: 'food' }) + .toCompileTo('food'); }); }); describe('#compilerInfo', function() { @@ -49,7 +53,9 @@ describe('javascript-compiler api', function() { throw new Error("It didn't work"); } }; - shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food '); + expectTemplate('{{foo}} ') + .withInput({ foo: 'food' }) + .toCompileTo('food '); }); }); describe('buffer', function() { @@ -70,7 +76,9 @@ describe('javascript-compiler api', function() { handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() { return this.quotedString('foo_'); }; - shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food '); + expectTemplate('{{foo}} ') + .withInput({ foo: 'food' }) + .toCompileTo('foo_food '); }); it('should allow append buffer override', function() { handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function( @@ -78,7 +86,9 @@ describe('javascript-compiler api', function() { ) { return $superAppend.call(this, [string, ' + "_foo"']); }; - shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo'); + expectTemplate('{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('food_foo'); }); }); diff --git a/spec/partials.js b/spec/partials.js index d00d4147e..df092267d 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -8,18 +8,18 @@ describe('partials', function() { { name: 'Alan', url: 'http://alan' } ] }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ' - ); - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, , false], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ' - ); + + expectTemplate(string) + .withInput(hash) + .withPartials({ dude: partial }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + + expectTemplate(string) + .withInput(hash) + .withPartials({ dude: partial }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); it('dynamic partials', function() { @@ -36,63 +36,48 @@ describe('partials', function() { return 'dude'; } }; - shouldCompileToWithPartials( - string, - [hash, helpers, { dude: partial }], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ' - ); - shouldCompileToWithPartials( - string, - [hash, helpers, { dude: partial }, , false], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ' - ); + + expectTemplate(string) + .withInput(hash) + .withHelpers(helpers) + .withPartials({ dude: partial }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + + expectTemplate(string) + .withInput(hash) + .withHelpers(helpers) + .withPartials({ dude: partial }) + .withRuntimeOptions({ data: false }) + .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'; - } - }; - shouldThrow( - function() { - shouldCompileToWithPartials( - string, - [hash, helpers, { dude: partial }], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ' - ); - }, - Handlebars.Exception, - 'The partial missing could not be found' - ); + }) + .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' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ', - 'Partials can be passed a context' - ); + 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) '); }); it('partials with no context', function() { @@ -103,98 +88,73 @@ describe('partials', function() { { name: 'Alan', url: 'http://alan' } ] }; - shouldCompileToWithPartials( - 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}', - [hash, {}, { dude: partial }, { explicitPartialContext: true }], - true, - 'Dudes: () () ' - ); - shouldCompileToWithPartials( - 'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}', - [hash, {}, { dude: partial }, { explicitPartialContext: true }], - true, - 'Dudes: foo () foo () ' - ); + + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') + .withInput(hash) + .withPartial('dude', partial) + .withCompileOptions({ explicitPartialContext: true }) + .toCompileTo('Dudes: () () '); + + expectTemplate('Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}') + .withInput(hash) + .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 = {}; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: dudes' - ); + 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 = {}; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: Empty' - ); + expectTemplate('Dudes: {{>dude dudes}}') + .withPartial('dude', '{{foo}} Empty') + .toCompileTo('Dudes: Empty'); }); it('partials with duplicate parameters', function() { - shouldThrow( - function() { - CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}'); - }, + expectTemplate('Dudes: {{>dude dudes foo bar=baz}}').toThrow( Error, 'Unsupported number of partial arguments: 2 - 1:7' ); }); 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' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ', - 'Basic partials output based on current context.' - ); + 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' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude, url: url }], - true, - 'Dudes: Yehuda http://yehuda Alan http://alan ', - 'Partials are rendered inside of other partials' - ); + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}} {{> url}} ', + url: '{{url}}' + }) + .withMessage('Partials are rendered inside of other partials') + .toCompileTo( + 'Dudes: Yehuda http://yehuda Alan http://alan ' + ); }); it('rendering undefined partial throws an exception', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{> whatever}}'); - template(); - }, + expectTemplate('{{> whatever}}').toThrow( Handlebars.Exception, 'The partial whatever could not be found' ); @@ -212,87 +172,60 @@ describe('partials', function() { }); it('rendering template partial in vm mode throws an exception', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{> whatever}}'); - template(); - }, + expectTemplate('{{> whatever}}').toThrow( Handlebars.Exception, 'The partial whatever could not be found' ); }); 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' } - ] - }; - shouldCompileTo( - string, - [hash, {}, { dude: partial }], - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ', - 'Function partials output based in VM.' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude }], - true, - 'Dudes: Jeepers Creepers', - 'Regular selectors can follow a partial' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { 'shared/dude': dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal paths' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { 'shared/dude.thing': dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal with points in paths' - ); + 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'); }); it('Global Partials', function() { handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}'); - var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; - var dude = '{{name}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - shouldCompileToWithPartials( - string, - [hash, {}, { 'shared/dude': dude }], - true, - 'Dudes: Jeepers Creepers', - 'Partials can use globals or passed' - ); + 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'); handlebarsEnv.unregisterPartial('globalTest'); equals(handlebarsEnv.partials.globalTest, undefined); @@ -304,408 +237,317 @@ describe('partials', function() { globalTest: '{{anotherDude}}' }); - var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; - var hash = { name: 'Jeepers', anotherDude: 'Creepers' }; - shouldCompileToWithPartials( - string, - [hash], - true, - 'Dudes: Jeepers Creepers', - 'Partials can use globals or passed' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { 404: dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal paths' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { '404/asdf?.bar': dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal paths' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { '+404/asdf?.bar': dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal paths' - ); + 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' }; - shouldCompileToWithPartials( - string, - [hash, {}, { '+404/asdf?.bar': dude }], - true, - 'Dudes: Jeepers', - 'Partials can use literal paths' - ); + 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' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '') + .toCompileTo('Dudes: '); }); it('throw on missing partial', function() { var compile = handlebarsEnv.compile; + var compileWithPartial = CompilerContext.compileWithPartial; handlebarsEnv.compile = undefined; - shouldThrow( - function() { - shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], ''); - }, - Error, - /The partial dude could not be compiled/ - ); + CompilerContext.compileWithPartial = CompilerContext.compile; + expectTemplate('{{> dude}}') + .withPartials({ dude: 'fail' }) + .toThrow(Error, /The partial dude could not be compiled/); handlebarsEnv.compile = compile; + CompilerContext.compileWithPartial = compileWithPartial; }); describe('partial blocks', function() { it('should render partial block as default', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, {}], - true, - 'success' - ); + expectTemplate('{{#> dude}}success{{/dude}}').toCompileTo('success'); }); + it('should execute default block with proper context', function() { - shouldCompileToWithPartials( - '{{#> dude context}}{{value}}{{/dude}}', - [{ context: { value: 'success' } }, {}, {}], - true, - 'success' - ); + expectTemplate('{{#> dude context}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .toCompileTo('success'); }); + it('should propagate block parameters to default block', function() { - shouldCompileToWithPartials( - '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', - [{ context: { value: 'success' } }, {}, {}], - true, - 'success' - ); + expectTemplate( + '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' + ) + .withInput({ context: { value: 'success' } }) + .toCompileTo('success'); }); it('should not use partial block if partial exists', function() { - shouldCompileToWithPartials( - '{{#> dude}}fail{{/dude}}', - [{}, {}, { dude: 'success' }], - true, - 'success' - ); + expectTemplate('{{#> dude}}fail{{/dude}}') + .withPartials({ dude: 'success' }) + .toCompileTo('success'); }); it('should render block from partial', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, { dude: '{{> @partial-block }}' }], - true, - 'success' - ); + expectTemplate('{{#> dude}}success{{/dude}}') + .withPartials({ dude: '{{> @partial-block }}' }) + .toCompileTo('success'); }); + it('should be able to render the partial-block twice', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }], - true, - 'success success' - ); + expectTemplate('{{#> dude}}success{{/dude}}') + .withPartials({ dude: '{{> @partial-block }} {{> @partial-block }}' }) + .toCompileTo('success success'); }); + it('should render block from partial with context', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{#with context}}{{> @partial-block }}{{/with}}' } - ], - true, - 'success' - ); + expectTemplate('{{#> dude}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: '{{#with context}}{{> @partial-block }}{{/with}}' + }) + .toCompileTo('success'); }); it('should be able to access the @data frame from a partial-block', function() { - shouldCompileToWithPartials( - '{{#> dude}}in-block: {{@root/value}}{{/dude}}', - [ - { value: 'success' }, - {}, - { - dude: - 'before-block: {{@root/value}} {{> @partial-block }}
'
- }
- ],
- true,
- 'before-block: success in-block: success
'
- );
+ expectTemplate('{{#> dude}}in-block: {{@root/value}}{{/dude}}')
+ .withInput({ value: 'success' })
+ .withPartials({
+ dude:
+ 'before-block: {{@root/value}} {{> @partial-block }}
'
+ })
+ .toCompileTo('before-block: success in-block: success
');
});
it('should allow the #each-helper to be used along with partial-blocks', function() {
- shouldCompileToWithPartials(
- '{{#> list value}}value = {{.}}{{/list}}',
- [
- { value: ['a', 'b', 'c'] },
- {},
- {
- list:
- '