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: &lt;b&gt;', - 'escaping should properly handle amperstands' - ); + expectTemplate('{{awesome}}') + .withInput({ awesome: 'Escaped, looks like: <b>' }) + .withMessage('escaping should properly handle amperstands') + .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); }); it("functions returning safestrings shouldn't be escaped", function() { - 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 '
' + options.fn(this) + '
'; - } - } - } - ); - equal( - result, - '

Yehuda

', - 'Block helper executed with current context' - ); + expectTemplate('{{#form}}

{{name}}

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

Yehuda

'); }); it('block helper should have context in this', function() { - var source = - ''; function link(options) { return '' + options.fn(this) + ''; } - var data = { - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 } - ] - }; - shouldCompileTo( - source, - [data, { link: link }], - '' - ); + expectTemplate( + '' + ) + .withInput({ + people: [ + { name: 'Alan', id: 1 }, + { name: 'Yehuda', id: 2 } + ] + }) + .withHelper('link', link) + .toCompileTo( + '' + ); }); it('block helper for undefined value', function() { - shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, ''); + expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo(''); }); it('block helper passing a new context', function() { - var string = '{{#form yehuda}}

{{name}}

{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { yehuda: { name: 'Yehuda' } }, - { - helpers: { - form: function(context, options) { - return '
' + options.fn(context) + '
'; - } - } - } - ); - equal(result, '

Yehuda

', 'Context variable resolved'); + expectTemplate('{{#form yehuda}}

{{name}}

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

Yehuda

'); }); 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 '
' + options.fn(context) + '
'; - } - } - } - ); - equal( - result, - '

Harold

', - 'Complex path variable resolved' - ); + expectTemplate('{{#form yehuda/cat}}

{{name}}

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

Harold

'); }); 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 '
' + options.fn(context) + '
'; - } - } - } - ); - equal( - result, - '

Yehuda

Hello
', - 'Both blocks executed' - ); + }) + .withHelper('link', function(options) { + return '' + options.fn(this) + ''; + }) + .withHelper('form', function(context, options) { + return '
' + options.fn(context) + '
'; + }) + .withMessage('Both blocks executed') + .toCompileTo('

Yehuda

Hello
'); }); 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 }], - '', - 'an inverse wrapper is passed in as a new context' - ); - shouldCompileTo( - string, - [empty, { 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(''); + + expectTemplate(string) + .withInput({ people: [] }) + .withHelpers({ list: list }) + .withMessage('an inverse wrapper can be optionally called') + .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( - '', - [ - { value: ['a', 'b', 'c'] }, - {}, - { - list: - '{{#each .}}{{> @partial-block}}{{/each}}' - } - ], - true, - '' - ); + expectTemplate( + '' + ) + .withInput({ + value: ['a', 'b', 'c'] + }) + .withPartials({ + list: + '{{#each .}}{{> @partial-block}}{{/each}}' + }) + .toCompileTo( + '' + ); }); + it('should render block from partial with context (twice)', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { - dude: - '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' - } - ], - true, - 'success success' - ); + expectTemplate('{{#> dude}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: + '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' + }) + .toCompileTo('success success'); }); + it('should render block from partial with context', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{../context/value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{#with context}}{{> @partial-block }}{{/with}}' } - ], - true, - 'success' - ); + expectTemplate('{{#> dude}}{{../context/value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: '{{#with context}}{{> @partial-block }}{{/with}}' + }) + .toCompileTo('success'); }); + it('should render block from partial with block params', function() { - shouldCompileToWithPartials( - '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{> @partial-block }}' } - ], - true, - 'success' - ); + expectTemplate( + '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' + ) + .withInput({ context: { value: 'success' } }) + .withPartials({ dude: '{{> @partial-block }}' }) + .toCompileTo('success'); }); + it('should render nested partial blocks', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}}{{/nested}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}}{{/nested}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks at different nesting levels', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks at different nesting levels (twice)', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks (twice at each level)', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}', - nested: '{{> @partial-block}}{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}', + nested: '{{> @partial-block}}{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); }); describe('inline partials', function() { it('should define inline partials for template', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ).toCompileTo('success'); }); + it('should overwrite multiple partials in the same template', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ).toCompileTo('success'); }); + it('should define inline partials for block', function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', - {}, - 'success' - ); - shouldThrow( - function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', - {}, - 'success' - ); - }, - Error, - /myPartial could not/ - ); + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' + ).toCompileTo('success'); + + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}' + ).toThrow(Error, /myPartial could not/); }); + it('should override global partials', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - { - hash: {}, - partials: { - myPartial: function() { - return 'fail'; - } + expectTemplate( + '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ) + .withPartials({ + myPartial: function() { + return 'fail'; } - }, - 'success' - ); + }) + .toCompileTo('success'); }); + it('should override template partials', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' + ).toCompileTo('success'); }); + it('should override partials down the entire stack', function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', - {}, - 'success' - ); + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}' + ).toCompileTo('success'); }); it('should define inline partials for partial call', function() { - shouldCompileToWithPartials( - '{{#*inline "myPartial"}}success{{/inline}}{{> dude}}', - [{}, {}, { dude: '{{> myPartial }}' }], - true, - 'success' - ); + expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> dude}}') + .withPartials({ dude: '{{> myPartial }}' }) + .toCompileTo('success'); }); + it('should define inline partials in partial block call', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}', - [{}, {}, { dude: '{{> myPartial }}' }], - true, - 'success' - ); + expectTemplate( + '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}' + ) + .withPartials({ dude: '{{> myPartial }}' }) + .toCompileTo('success'); }); + it('should render nested inline partials', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'success' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo('success'); }); + it('should render nested inline partials with partial-blocks on different nesting levels', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{>@partial-block}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'successsuccess' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo( + 'successsuccess' + ); }); + it('should render nested inline partials (twice at each level)', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}} {{>@partial-block}}{{/inner}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'success successsuccess success' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo( + 'success successsuccess success' + ); }); }); @@ -720,125 +562,119 @@ describe('partials', function() { describe('standalone partials', function() { it('indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude }], - true, - 'Dudes:\n Yehuda\n Alan\n' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{name}}\n') + .toCompileTo('Dudes:\n Yehuda\n Alan\n'); }); + it('nested indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n {{> url}}'; - var url = '{{url}}!\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude, url: url }], - true, - 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' + }) + .toCompileTo( + 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n' + ); }); + it('prevent nested indented partials', function() { - var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; - var dude = '{{name}}\n {{> url}}'; - var url = '{{url}}!\n'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude, url: url }, { preventIndent: true }], - true, - 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' + }) + .withCompileOptions({ preventIndent: true }) + .toCompileTo( + 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n' + ); }); }); describe('compat mode', function() { it('partials can access parents', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, true], - true, - 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withCompileOptions({ compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); + it('partials can access parents with custom context', function() { - var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, true], - true, - 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withCompileOptions({ compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); + it('partials can access parents without data', function() { - var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, true, false], - true, - 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false, compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); + it('partials inherit compat', function() { - var string = 'Dudes: {{> dude}}'; - var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}'; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, true], - true, - 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' - ); + expectTemplate('Dudes: {{> dude}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}' + }) + .withCompileOptions({ compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); }); }); diff --git a/spec/regressions.js b/spec/regressions.js index f6147ad6e..86d4634d2 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -1,61 +1,53 @@ describe('Regressions', function() { it('GH-94: Cannot read property of undefined', function() { - var data = { - books: [ - { - title: 'The origin of species', - author: { - name: 'Charles Darwin' + expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}') + .withInput({ + books: [ + { + title: 'The origin of species', + author: { + name: 'Charles Darwin' + } + }, + { + title: 'Lazarillo de Tormes' } - }, - { - title: 'Lazarillo de Tormes' - } - ] - }; - var string = '{{#books}}{{title}}{{author.name}}{{/books}}'; - shouldCompileTo( - string, - data, - 'The origin of speciesCharles DarwinLazarillo de Tormes', - 'Renders without an undefined property error' - ); + ] + }) + .withMessage('Renders without an undefined property error') + .toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes'); }); it("GH-150: Inverted sections print when they shouldn't", function() { var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; - shouldCompileTo( - string, - {}, - 'not set :: ', - "inverted sections run when property isn't present in context" - ); - shouldCompileTo( - string, - { set: undefined }, - 'not set :: ', - 'inverted sections run when property is undefined' - ); - shouldCompileTo( - string, - { set: false }, - 'not set :: ', - 'inverted sections run when property is false' - ); - shouldCompileTo( - string, - { set: true }, - ' :: set', - "inverted sections don't run when property is true" - ); + expectTemplate(string) + .withMessage( + "inverted sections run when property isn't present in context" + ) + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: undefined }) + .withMessage('inverted sections run when property is undefined') + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: false }) + .withMessage('inverted sections run when property is false') + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: true }) + .withMessage("inverted sections don't run when property is true") + .toCompileTo(' :: set'); }); it('GH-158: Using array index twice, breaks the template', function() { - var string = '{{arr.[0]}}, {{arr.[1]}}'; - var data = { arr: [1, 2] }; - - shouldCompileTo(string, data, '1, 2', 'it works as expected'); + expectTemplate('{{arr.[0]}}, {{arr.[1]}}') + .withInput({ arr: [1, 2] }) + .withMessage('it works as expected') + .toCompileTo('1, 2'); }); it("bug reported by @fat where lambdas weren't being properly resolved", function() { @@ -73,6 +65,7 @@ describe('Regressions', function() { '\n' + 'Nothing to check out...\n' + '{{/hasThings}}'; + var data = { thing: function() { return 'blah'; @@ -95,25 +88,22 @@ describe('Regressions', function() { '
  • @dhg
  • \n' + '
  • @sayrer
  • \n' + '.\n'; - shouldCompileTo(string, data, output); + + expectTemplate(string) + .withInput(data) + .toCompileTo(output); }); it('GH-408: Multiple loops fail', function() { - var context = [ - { name: 'John Doe', location: { city: 'Chicago' } }, - { name: 'Jane Doe', location: { city: 'New York' } } - ]; - - var template = CompilerContext.compile( + expectTemplate( '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}' - ); - - var result = template(context); - equals( - result, - 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', - 'It should output multiple times' - ); + ) + .withInput([ + { name: 'John Doe', location: { city: 'Chicago' } }, + { name: 'Jane Doe', location: { city: 'New York' } } + ]) + .withMessage('It should output multiple times') + .toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe'); }); it('GS-428: Nested if else rendering', function() { @@ -131,259 +121,242 @@ describe('Regressions', function() { } }; - shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); - shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); + expectTemplate(succeedingTemplate) + .withHelpers(helpers) + .toCompileTo(' Expected '); + + expectTemplate(failingTemplate) + .withHelpers(helpers) + .toCompileTo(' Expected '); }); it('GH-458: Scoped this identifier', function() { - shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar'); + expectTemplate('{{./foo}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); }); it('GH-375: Unicode line terminators', function() { - shouldCompileTo('\u2028', {}, '\u2028'); + expectTemplate('\u2028').toCompileTo('\u2028'); }); it('GH-534: Object prototype aliases', function() { /* eslint-disable no-extend-native */ Object.prototype[0xd834] = true; - shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); + expectTemplate('{{foo}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); delete Object.prototype[0xd834]; /* eslint-enable no-extend-native */ }); it('GH-437: Matching escaping', function() { - shouldThrow(function() { - CompilerContext.compile('{{{a}}'); - }, Error); - shouldThrow(function() { - CompilerContext.compile('{{a}}}'); - }, Error); + expectTemplate('{{{a}}').toThrow(Error, /Parse error on/); + expectTemplate('{{a}}}').toThrow(Error, /Parse error on/); }); it('GH-676: Using array in escaping mustache fails', function() { - var string = '{{arr}}'; var data = { arr: [1, 2] }; - shouldCompileTo(string, data, data.arr.toString(), 'it works as expected'); + expectTemplate('{{arr}}') + .withInput(data) + .withMessage('it works as expected') + .toCompileTo(data.arr.toString()); }); it('Mustache man page', function() { - var string = - 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}'; - var data = { - name: 'Chris', - value: 10000, - taxed_value: 10000 - 10000 * 0.4, - in_ca: true - }; - - shouldCompileTo( - string, - data, - 'Hello Chris. You have just won $10000! Well, $6000, after taxes.', - 'the hello world mustache example works' - ); + expectTemplate( + 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}' + ) + .withInput({ + name: 'Chris', + value: 10000, + taxed_value: 10000 - 10000 * 0.4, + in_ca: true + }) + .withMessage('the hello world mustache example works') + .toCompileTo( + 'Hello Chris. You have just won $10000! Well, $6000, after taxes.' + ); }); it('GH-731: zero context rendering', function() { - shouldCompileTo( - '{{#foo}} This is {{bar}} ~ {{/foo}}', - { foo: 0, bar: 'OK' }, - ' This is ~ ' - ); + expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}') + .withInput({ + foo: 0, + bar: 'OK' + }) + .toCompileTo(' This is ~ '); }); it('GH-820: zero pathed rendering', function() { - shouldCompileTo('{{foo.bar}}', { foo: 0 }, ''); + expectTemplate('{{foo.bar}}') + .withInput({ foo: 0 }) + .toCompileTo(''); }); it('GH-837: undefined values for helpers', function() { - var helpers = { - str: function(value) { - return value + ''; - } - }; - - shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined'); + expectTemplate('{{str bar.baz}}') + .withHelpers({ + str: function(value) { + return value + ''; + } + }) + .toCompileTo('undefined'); }); it('GH-926: Depths and de-dupe', function() { - var context = { - name: 'foo', - data: [1], - notData: [1] - }; - - var template = CompilerContext.compile( + expectTemplate( '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}' - ); - - var result = template(context); - equals(result, 'foo'); + ) + .withInput({ + name: 'foo', + data: [1], + notData: [1] + }) + .toCompileTo('foo'); }); it('GH-1021: Each empty string key', function() { - var data = { - '': 'foo', - name: 'Chris', - value: 10000 - }; - - shouldCompileTo( - '{{#each data}}Key: {{@key}}\n{{/each}}', - { data: data }, - 'Key: \nKey: name\nKey: value\n' - ); + expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}') + .withInput({ + data: { + '': 'foo', + name: 'Chris', + value: 10000 + } + }) + .toCompileTo('Key: \nKey: name\nKey: value\n'); }); it('GH-1054: Should handle simple safe string responses', function() { - var root = '{{#wrap}}{{>partial}}{{/wrap}}'; - var partials = { - partial: '{{#wrap}}{{/wrap}}' - }; - var helpers = { - wrap: function(options) { - return new Handlebars.SafeString(options.fn()); - } - }; - - shouldCompileToWithPartials( - root, - [{}, helpers, partials], - true, - '' - ); + expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') + .withHelpers({ + wrap: function(options) { + return new Handlebars.SafeString(options.fn()); + } + }) + .withPartials({ + partial: '{{#wrap}}{{/wrap}}' + }) + .toCompileTo(''); }); it('GH-1065: Sparse arrays', function() { var array = []; array[1] = 'foo'; array[3] = 'bar'; - shouldCompileTo( - '{{#each array}}{{@index}}{{.}}{{/each}}', - { array: array }, - '1foo3bar' - ); + expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}') + .withInput({ array: array }) + .toCompileTo('1foo3bar'); }); it('GH-1093: Undefined helper context', function() { - var obj = { foo: undefined, bar: 'bat' }; - var helpers = { - helper: function() { - // It's valid to execute a block against an undefined context, but - // helpers can not do so, so we expect to have an empty object here; - for (var name in this) { - if (Object.prototype.hasOwnProperty.call(this, name)) { - return 'found'; + expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}') + .withInput({ obj: { foo: undefined, bar: 'bat' } }) + .withHelpers({ + helper: function() { + // It's valid to execute a block against an undefined context, but + // helpers can not do so, so we expect to have an empty object here; + for (var name in this) { + if (Object.prototype.hasOwnProperty.call(this, name)) { + return 'found'; + } } + // And to make IE happy, check for the known string as length is not enumerated. + return this === 'bat' ? 'found' : 'not'; } - // And to make IE happy, check for the known string as length is not enumerated. - return this === 'bat' ? 'found' : 'not'; - } - }; - - shouldCompileTo( - '{{#each obj}}{{{helper}}}{{.}}{{/each}}', - [{ obj: obj }, helpers], - 'notfoundbat' - ); + }) + .toCompileTo('notfoundbat'); }); it('should support multiple levels of inline partials', function() { - var string = - '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'doctypelayoutsubcontent' - ); + expectTemplate( + '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}' + ) + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' + }) + .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1089: should support failover content in multiple levels of inline partials', function() { - var string = '{{#> layout}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'doctypelayoutsubcontent' - ); + expectTemplate('{{#> layout}}{{/layout}}') + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' + }) + .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1099: should support greater than 3 nested levels of inline partials', function() { - var string = '{{#> layout}}Outer{{/layout}}'; - var partials = { - layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', - inner: '' - }; - shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer'); + expectTemplate('{{#> layout}}Outer{{/layout}}') + .withPartials({ + layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', + inner: '' + }) + .toCompileTo('Outer'); }); it('GH-1135 : Context handling within each iteration', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - myif: function(conditional, options) { - if (conditional) { - return options.fn(this); - } else { - return options.inverse(this); - } - } - }; - - shouldCompileTo( + expectTemplate( '{{#each array}}\n' + ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' + ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' + - '{{/each}}', - [obj, helpers], - ' 1. IF: John--\n' + ' 2. MYIF: John==\n' - ); + '{{/each}}' + ) + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + myif: function(conditional, options) { + if (conditional) { + return options.fn(this); + } else { + return options.inverse(this); + } + } + }) + .toCompileTo(' 1. IF: John--\n' + ' 2. MYIF: John==\n'); }); it('GH-1186: Support block params for existing programs', function() { - var string = + expectTemplate( '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + - '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + - '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'; - - shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', ''); + '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + + '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' + ) + .withInput({ + listOne: ['a'], + listTwo: ['b'] + }) + .withMessage('') + .toCompileTo('ab'); }); it('GH-1319: "unless" breaks when "each" value equals "null"', function() { - var string = - '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; - shouldCompileTo( - string, - { value: 'parent', list: [null, 'a'] }, - 'parent=parent parent=parent ', - '' - ); + expectTemplate( + '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}' + ) + .withInput({ + value: 'parent', + list: [null, 'a'] + }) + .withMessage('') + .toCompileTo('parent=parent parent=parent '); }); it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { - var string = 'template {{>partial}} template'; - var partials = { - partialWithBlock: - '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', - partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'template block partial block template' - ); + expectTemplate('template {{>partial}} template') + .withPartials({ + partialWithBlock: + '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', + partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' + }) + .toCompileTo('template block partial block template'); }); describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() { @@ -482,14 +455,14 @@ describe('Regressions', function() { }); it('should allow hash with protected array names', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - helpa: function(options) { - return options.hash.length; - } - }; - - shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo'); + expectTemplate('{{helpa length="foo"}}') + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + helpa: function(options) { + return options.hash.length; + } + }) + .toCompileTo('foo'); }); describe('GH-1598: Performance degradation for partials since v4.3.0', function() { diff --git a/spec/security.js b/spec/security.js index 1b345f0cc..25aabeb7b 100644 --- a/spec/security.js +++ b/spec/security.js @@ -20,36 +20,19 @@ describe('security issues', function() { }); it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() { - shouldCompileTo( - '{{constructor.name}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); - shouldCompileTo( - '{{lookup (lookup this "constructor") "name"}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); + expectTemplate('{{constructor.name}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); + + expectTemplate('{{lookup (lookup this "constructor") "name"}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); }); it('should allow the "constructor" property to be accessed if it is an "own property"', function() { - shouldCompileTo( - '{{lookup (lookup this "constructor") "name"}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); + expectTemplate('{{lookup (lookup this "constructor") "name"}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); }); }); @@ -60,19 +43,13 @@ describe('security issues', function() { describe('without the option "allowExplicitCallOfHelperMissing"', function() { it('should throw an exception when calling "{{helperMissing}}" ', function() { - shouldThrow(function() { - var template = Handlebars.compile('{{helperMissing}}'); - template({}); - }, Error); + expectTemplate('{{helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { - shouldThrow(function() { - var template = Handlebars.compile( - '{{#helperMissing}}{{/helperMissing}}' - ); - template({}); - }, Error); + expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; expect(function() { @@ -85,17 +62,15 @@ describe('security issues', function() { }).to.throw(Error); expect(functionCalls.length).to.equal(0); }); + it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { - shouldThrow(function() { - var template = Handlebars.compile( - '{{#blockHelperMissing .}}{{/blockHelperMissing}}' - ); - template({ + expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}') + .withInput({ fn: function() { return 'functionInData'; } - }); - }, Error); + }) + .toThrow(Error); }); }); @@ -104,12 +79,14 @@ describe('security issues', function() { var template = Handlebars.compile('{{helperMissing}}'); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { var template = Handlebars.compile( '{{#helperMissing}}{{/helperMissing}}' ); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); @@ -123,6 +100,7 @@ describe('security issues', function() { ); equals(functionCalls.length, 1); }); + it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { var template = Handlebars.compile( '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}' diff --git a/spec/spec.js b/spec/spec.js index ff4d7bc98..8b4ea99fe 100644 --- a/spec/spec.js +++ b/spec/spec.js @@ -40,22 +40,12 @@ describe('spec', function() { /* eslint-enable no-eval */ } it(name + ' - ' + test.name, function() { - if (test.partials) { - shouldCompileToWithPartials( - test.template, - [data, {}, test.partials, true], - true, - test.expected, - test.desc + ' "' + test.template + '"' - ); - } else { - shouldCompileTo( - test.template, - [data, {}, {}, true], - test.expected, - test.desc + ' "' + test.template + '"' - ); - } + expectTemplate(test.template) + .withInput(data) + .withPartials(test.partials || {}) + .withCompileOptions({ compat: true }) + .withMessage(test.desc + ' "' + test.template + '"') + .toCompileTo(test.expected); }); }); }); diff --git a/spec/strict.js b/spec/strict.js index 680bbc0d8..8e48fab5e 100644 --- a/spec/strict.js +++ b/spec/strict.js @@ -3,161 +3,107 @@ var Exception = Handlebars.Exception; describe('strict', function() { describe('strict mode', function() { it('should error on missing property lookup', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{hello}}', { strict: true }); - - template({}); - }, - Exception, - /"hello" not defined in/ - ); + expectTemplate('{{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, /"hello" not defined in/); }); + it('should error on missing child', function() { - var template = CompilerContext.compile('{{hello.bar}}', { strict: true }); - equals(template({ hello: { bar: 'foo' } }), 'foo'); + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: { bar: 'foo' } }) + .toCompileTo('foo'); - shouldThrow( - function() { - template({ hello: {} }); - }, - Exception, - /"bar" not defined in/ - ); + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: {} }) + .toThrow(Exception, /"bar" not defined in/); }); - it('should handle explicit undefined', function() { - var template = CompilerContext.compile('{{hello.bar}}', { strict: true }); - equals(template({ hello: { bar: undefined } }), ''); + it('should handle explicit undefined', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: { bar: undefined } }) + .toCompileTo(''); }); + it('should error on missing property lookup in known helpers mode', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{hello}}', { - strict: true, - knownHelpersOnly: true - }); - - template({}); - }, - Exception, - /"hello" not defined in/ - ); + expectTemplate('{{hello}}') + .withCompileOptions({ + strict: true, + knownHelpersOnly: true + }) + .toThrow(Exception, /"hello" not defined in/); }); - it('should error on missing context', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{hello}}', { strict: true }); - template(); - }, Error); + it('should error on missing context', function() { + expectTemplate('{{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Error); }); it('should error on missing data lookup', function() { - var template = CompilerContext.compile('{{@hello}}', { strict: true }); - equals(template(undefined, { data: { hello: 'foo' } }), 'foo'); + var xt = expectTemplate('{{@hello}}').withCompileOptions({ + strict: true + }); - shouldThrow(function() { - template(); - }, Error); + xt.toThrow(Error); + + xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo'); }); it('should not run helperMissing for helper calls', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{hello foo}}', { - strict: true - }); - - template({ foo: true }); - }, - Exception, - /"hello" not defined in/ - ); - - shouldThrow( - function() { - var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', { - strict: true - }); - - template({ foo: true }); - }, - Exception, - /"hello" not defined in/ - ); + expectTemplate('{{hello foo}}') + .withCompileOptions({ strict: true }) + .withInput({ foo: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{#hello foo}}{{/hello}}') + .withCompileOptions({ strict: true }) + .withInput({ foo: true }) + .toThrow(Exception, /"hello" not defined in/); }); + it('should throw on ambiguous blocks', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{#hello}}{{/hello}}', { - strict: true - }); - - template({}); - }, - Exception, - /"hello" not defined in/ - ); - - shouldThrow( - function() { - var template = CompilerContext.compile('{{^hello}}{{/hello}}', { - strict: true - }); - - template({}); - }, - Exception, - /"hello" not defined in/ - ); - - shouldThrow( - function() { - var template = CompilerContext.compile( - '{{#hello.bar}}{{/hello.bar}}', - { strict: true } - ); - - template({ hello: {} }); - }, - Exception, - /"bar" not defined in/ - ); + expectTemplate('{{#hello}}{{/hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{^hello}}{{/hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{#hello.bar}}{{/hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: {} }) + .toThrow(Exception, /"bar" not defined in/); }); it('should allow undefined parameters when passed to helpers', function() { - var template = CompilerContext.compile( - '{{#unless foo}}success{{/unless}}', - { strict: true } - ); - equals(template({}), 'success'); + expectTemplate('{{#unless foo}}success{{/unless}}') + .withCompileOptions({ strict: true }) + .toCompileTo('success'); }); it('should allow undefined hash when passed to helpers', function() { - var template = CompilerContext.compile('{{helper value=@foo}}', { - strict: true - }); - var helpers = { - helper: function(options) { - equals('value' in options.hash, true); - equals(options.hash.value, undefined); - return 'success'; - } - }; - equals(template({}, { helpers: helpers }), 'success'); + expectTemplate('{{helper value=@foo}}') + .withCompileOptions({ + strict: true + }) + .withHelpers({ + helper: function(options) { + equals('value' in options.hash, true); + equals(options.hash.value, undefined); + return 'success'; + } + }) + .toCompileTo('success'); }); it('should show error location on missing property lookup', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('\n\n\n {{hello}}', { - strict: true - }); - template({}); - }, - Exception, - '"hello" not defined in [object Object] - 4:5' - ); + expectTemplate('\n\n\n {{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, '"hello" not defined in [object Object] - 4:5'); }); it('should error contains correct location properties on missing property lookup', function() { @@ -177,54 +123,42 @@ describe('strict', function() { describe('assume objects', function() { it('should ignore missing property', function() { - var template = CompilerContext.compile('{{hello}}', { - assumeObjects: true - }); - - equal(template({}), ''); + expectTemplate('{{hello}}') + .withCompileOptions({ assumeObjects: true }) + .toCompileTo(''); }); - it('should ignore missing child', function() { - var template = CompilerContext.compile('{{hello.bar}}', { - assumeObjects: true - }); - equal(template({ hello: {} }), ''); + it('should ignore missing child', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .withInput({ hello: {} }) + .toCompileTo(''); }); - it('should error on missing object', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{hello.bar}}', { - assumeObjects: true - }); - template({}); - }, Error); + it('should error on missing object', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .toThrow(Error); }); - it('should error on missing context', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{hello}}', { - assumeObjects: true - }); - template(); - }, Error); + it('should error on missing context', function() { + expectTemplate('{{hello}}') + .withCompileOptions({ assumeObjects: true }) + .withInput(undefined) + .toThrow(Error); }); it('should error on missing data lookup', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{@hello.bar}}', { - assumeObjects: true - }); - - template(); - }, Error); + expectTemplate('{{@hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .withInput(undefined) + .toThrow(Error); }); it('should execute blockHelperMissing', function() { - var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', { - assumeObjects: true - }); - - equals(template({}), 'foo'); + expectTemplate('{{^hello}}foo{{/hello}}') + .withCompileOptions({ assumeObjects: true }) + .toCompileTo('foo'); }); }); }); diff --git a/spec/string-params.js b/spec/string-params.js index 9f83611a4..c4b9a27b7 100644 --- a/spec/string-params.js +++ b/spec/string-params.js @@ -1,151 +1,117 @@ describe('string params mode', function() { it('arguments to helpers can be retrieved from options hash in string form', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}', { - stringParams: true - }); - - var helpers = { - wycats: function(passiveVoice, noun) { - return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun; - } - }; - - var result = template({}, { helpers: helpers }); - - equals( - result, - 'HELP ME MY BOSS is.a slave.driver', - 'String parameters output' - ); + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun) { + return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun; + } + }) + .withMessage('String parameters output') + .toCompileTo('HELP ME MY BOSS is.a slave.driver'); }); it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() { - var template = CompilerContext.compile( - '{{#wycats is.a slave.driver}}help :({{/wycats}}', - { stringParams: true } - ); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - return ( - 'HELP ME MY BOSS ' + - passiveVoice + - ' ' + - noun + - ': ' + - options.fn(this) - ); - } - }; - - var result = template({}, { helpers: helpers }); - - equals( - result, - 'HELP ME MY BOSS is.a slave.driver: help :(', - 'String parameters output' - ); + expectTemplate('{{#wycats is.a slave.driver}}help :({{/wycats}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ': ' + + options.fn(this) + ); + } + }) + .withMessage('String parameters output') + .toCompileTo('HELP ME MY BOSS is.a slave.driver: help :('); }); it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() { - var template = CompilerContext.compile( - '{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', - { stringParams: true } - ); - - var helpers = { - tomdale: function(desire, noun, options) { - return ( - 'STOP ME FROM READING HACKER NEWS I ' + - options.contexts[0][desire] + - ' ' + - noun - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template( - { + expectTemplate('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + ); + }, + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ dale: {}, need: 'need-a' - }, - { helpers: helpers } - ); - - equals( - result, - 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', - 'Proper context variable output' - ); + }) + .withMessage('Proper context variable output') + .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke'); }); it('information about the types is passed along', function() { - var template = CompilerContext.compile( - "{{tomdale 'need' dad.joke true false}}", - { stringParams: true } - ); - - var helpers = { - tomdale: function(desire, noun, trueBool, falseBool, options) { - equal(options.types[0], 'StringLiteral', 'the string type is passed'); - equal( - options.types[1], - 'PathExpression', - 'the expression type is passed' - ); - equal( - options.types[2], - 'BooleanLiteral', - 'the expression type is passed' - ); - equal(desire, 'need', 'the string form is passed for strings'); - equal(noun, 'dad.joke', 'the string form is passed for expressions'); - equal(trueBool, true, 'raw booleans are passed through'); - equal(falseBool, false, 'raw booleans are passed through'); - return 'Helper called'; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, 'Helper called'); + expectTemplate("{{tomdale 'need' dad.joke true false}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, trueBool, falseBool, options) { + equal(options.types[0], 'StringLiteral', 'the string type is passed'); + equal( + options.types[1], + 'PathExpression', + 'the expression type is passed' + ); + equal( + options.types[2], + 'BooleanLiteral', + 'the expression type is passed' + ); + equal(desire, 'need', 'the string form is passed for strings'); + equal(noun, 'dad.joke', 'the string form is passed for expressions'); + equal(trueBool, true, 'raw booleans are passed through'); + equal(falseBool, false, 'raw booleans are passed through'); + return 'Helper called'; + } + }) + .toCompileTo('Helper called'); }); it('hash parameters get type information', function() { - var template = CompilerContext.compile( - "{{tomdale he.says desire='need' noun=dad.joke bool=true}}", - { stringParams: true } - ); - - var helpers = { - tomdale: function(exclamation, options) { - equal(exclamation, 'he.says'); - equal(options.types[0], 'PathExpression'); - - equal(options.hashTypes.desire, 'StringLiteral'); - equal(options.hashTypes.noun, 'PathExpression'); - equal(options.hashTypes.bool, 'BooleanLiteral'); - equal(options.hash.desire, 'need'); - equal(options.hash.noun, 'dad.joke'); - equal(options.hash.bool, true); - return 'Helper called'; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, 'Helper called'); + expectTemplate("{{tomdale he.says desire='need' noun=dad.joke bool=true}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(exclamation, options) { + equal(exclamation, 'he.says'); + equal(options.types[0], 'PathExpression'); + + equal(options.hashTypes.desire, 'StringLiteral'); + equal(options.hashTypes.noun, 'PathExpression'); + equal(options.hashTypes.bool, 'BooleanLiteral'); + equal(options.hash.desire, 'need'); + equal(options.hash.noun, 'dad.joke'); + equal(options.hash.bool, true); + return 'Helper called'; + } + }) + .toCompileTo('Helper called'); }); it('hash parameters get context information', function() { - var template = CompilerContext.compile( - "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}", - { stringParams: true } - ); - var context = { dale: {} }; var helpers = { @@ -165,82 +131,77 @@ describe('string params mode', function() { } }; - var result = template(context, { helpers: helpers }); - equal(result, 'Helper called'); + expectTemplate( + "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}" + ) + .withCompileOptions({ stringParams: true }) + .withHelpers(helpers) + .withInput(context) + .toCompileTo('Helper called'); }); it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() { - var template = CompilerContext.compile( - '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', - { stringParams: true } - ); - - var helpers = { - tomdale: function(desire, noun, options) { - return ( - 'STOP ME FROM READING HACKER NEWS I ' + - options.contexts[0][desire] + - ' ' + - noun + - ' ' + - options.fn(this) - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template( - { + expectTemplate( + '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + + ' ' + + options.fn(this) + ); + }, + + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ dale: {}, need: 'need-a' - }, - { helpers: helpers } - ); - - equals( - result, - 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', - 'Proper context variable output' - ); + }) + .withMessage('Proper context variable output') + .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke wot'); }); it('with nested block ambiguous', function() { - var template = CompilerContext.compile( - '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', - { stringParams: true } - ); - - var helpers = { - with: function() { - return 'WITH'; - }, - view: function() { - return 'VIEW'; - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'WITH'); + expectTemplate( + '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + with: function() { + return 'WITH'; + }, + view: function() { + return 'VIEW'; + } + }) + .toCompileTo('WITH'); }); it('should handle DATA', function() { - var template = CompilerContext.compile('{{foo @bar}}', { - stringParams: true - }); - - var helpers = { - foo: function(bar, options) { - equal(bar, '@bar'); - equal(options.types[0], 'PathExpression'); - return 'Foo!'; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, 'Foo!'); + expectTemplate('{{foo @bar}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + foo: function(bar, options) { + equal(bar, '@bar'); + equal(options.types[0], 'PathExpression'); + return 'Foo!'; + } + }) + .toCompileTo('Foo!'); }); }); diff --git a/spec/subexpressions.js b/spec/subexpressions.js index 228510511..21b93aab9 100644 --- a/spec/subexpressions.js +++ b/spec/subexpressions.js @@ -1,61 +1,57 @@ describe('subexpressions', function() { it('arg-less helper', function() { - var string = '{{foo (bar)}}!'; - var context = {}; - var helpers = { - foo: function(val) { - return val + val; - }, - bar: function() { - return 'LOL'; - } - }; - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); + expectTemplate('{{foo (bar)}}!') + .withHelpers({ + foo: function(val) { + return val + val; + }, + bar: function() { + return 'LOL'; + } + }) + .toCompileTo('LOLLOL!'); }); it('helper w args', function() { - var string = '{{blog (equal a b)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate('{{blog (equal a b)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('mixed paths and helpers', function() { - var string = '{{blog baz.bat (equal a b) baz.bar}}'; - - var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }; - var helpers = { - blog: function(val, that, theOther) { - return 'val is ' + val + ', ' + that + ' and ' + theOther; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!'); + expectTemplate('{{blog baz.bat (equal a b) baz.bar}}') + .withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }) + .withHelpers({ + blog: function(val, that, theOther) { + return 'val is ' + val + ', ' + that + ' and ' + theOther; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is foo!, true and bar!'); }); it('supports much nesting', function() { - var string = '{{blog (equal (equal true true) true)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate('{{blog (equal (equal true true) true)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('GH-800 : Complex subexpressions', function() { @@ -69,20 +65,33 @@ describe('subexpressions', function() { } }; - shouldCompileTo( - "{{dash 'abc' (concat a b)}}", - [context, helpers], - 'abc-ab' - ); - shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab'); - shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab'); - shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c'); - shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c'); + expectTemplate("{{dash 'abc' (concat a b)}}") + .withInput(context) + .withHelpers(helpers) + .toCompileTo('abc-ab'); + + expectTemplate('{{dash d (concat a b)}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('d-ab'); + + expectTemplate('{{dash c.c (concat a b)}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('c-ab'); + + expectTemplate('{{dash (concat a b) c.c}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('ab-c'); + + expectTemplate('{{dash (concat a e.e) c.c}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('ae-c'); }); it('provides each nested helper invocation its own options hash', function() { - var string = '{{equal (equal true true) true}}'; - var lastOptions = null; var helpers = { equal: function(x, y, options) { @@ -93,200 +102,177 @@ describe('subexpressions', function() { return x === y; } }; - shouldCompileTo(string, [{}, helpers], 'true'); + expectTemplate('{{equal (equal true true) true}}') + .withHelpers(helpers) + .toCompileTo('true'); }); it('with hashes', function() { - var string = "{{blog (equal (equal true true) true fun='yes')}}"; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate("{{blog (equal (equal true true) true fun='yes')}}") + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('as hashes', function() { - var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}"; - - var helpers = { - blog: function(options) { - return 'val is ' + options.hash.fun; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [{}, helpers], 'val is true'); + expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}") + .withHelpers({ + blog: function(options) { + return 'val is ' + options.hash.fun; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('multiple subexpressions in a hash', function() { - var string = - '{{input aria-label=(t "Name") placeholder=(t "Example User")}}'; - - var helpers = { - input: function(options) { - var hash = options.hash; - var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); - var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); - return new Handlebars.SafeString( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - shouldCompileTo( - string, - [{}, helpers], - '' - ); + expectTemplate( + '{{input aria-label=(t "Name") placeholder=(t "Example User")}}' + ) + .withHelpers({ + input: function(options) { + var hash = options.hash; + var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); + var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); + return new Handlebars.SafeString( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) + .toCompileTo(''); }); it('multiple subexpressions in a hash with context', function() { - var string = - '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}'; - - var context = { - item: { - field: 'Name', - placeholder: 'Example User' - } - }; - - var helpers = { - input: function(options) { - var hash = options.hash; - var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); - var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); - return new Handlebars.SafeString( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - shouldCompileTo( - string, - [context, helpers], - '' - ); + expectTemplate( + '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}' + ) + .withInput({ + item: { + field: 'Name', + placeholder: 'Example User' + } + }) + .withHelpers({ + input: function(options) { + var hash = options.hash; + var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); + var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); + return new Handlebars.SafeString( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) + .toCompileTo(''); }); it('in string params mode,', function() { - var template = CompilerContext.compile( - '{{snog (blorg foo x=y) yeah a=b}}', - { stringParams: true } - ); - - var helpers = { - snog: function(a, b, options) { - equals(a, 'foo'); - equals( - options.types.length, - 2, - 'string params for outer helper processed correctly' - ); - equals( - options.types[0], - 'SubExpression', - 'string params for outer helper processed correctly' - ); - equals( - options.types[1], - 'PathExpression', - 'string params for outer helper processed correctly' - ); - return a + b; - }, - - blorg: function(a, options) { - equals( - options.types.length, - 1, - 'string params for inner helper processed correctly' - ); - equals( - options.types[0], - 'PathExpression', - 'string params for inner helper processed correctly' - ); - return a; - } - }; + expectTemplate('{{snog (blorg foo x=y) yeah a=b}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + snog: function(a, b, options) { + equals(a, 'foo'); + equals( + options.types.length, + 2, + 'string params for outer helper processed correctly' + ); + equals( + options.types[0], + 'SubExpression', + 'string params for outer helper processed correctly' + ); + equals( + options.types[1], + 'PathExpression', + 'string params for outer helper processed correctly' + ); + return a + b; + }, - var result = template( - { + blorg: function(a, options) { + equals( + options.types.length, + 1, + 'string params for inner helper processed correctly' + ); + equals( + options.types[0], + 'PathExpression', + 'string params for inner helper processed correctly' + ); + return a; + } + }) + .withInput({ foo: {}, yeah: {} - }, - { helpers: helpers } - ); - - equals(result, 'fooyeah'); + }) + .toCompileTo('fooyeah'); }); it('as hashes in string params mode', function() { - var template = CompilerContext.compile('{{blog fun=(bork)}}', { - stringParams: true - }); - - var helpers = { - blog: function(options) { - equals(options.hashTypes.fun, 'SubExpression'); - return 'val is ' + options.hash.fun; - }, - bork: function() { - return 'BORK'; - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'val is BORK'); + expectTemplate('{{blog fun=(bork)}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + blog: function(options) { + equals(options.hashTypes.fun, 'SubExpression'); + return 'val is ' + options.hash.fun; + }, + bork: function() { + return 'BORK'; + } + }) + .toCompileTo('val is BORK'); }); it('subexpression functions on the context', function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: function() { - return 'LOL'; - } - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: function() { + return 'LOL'; + } + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) + .toCompileTo('LOLLOL!'); }); it("subexpressions can't just be property lookups", function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: 'LOL' - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - shouldThrow(function() { - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); - }); + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: 'LOL' + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) + .toThrow(); }); }); diff --git a/spec/track-ids.js b/spec/track-ids.js index f7ad7abda..033e7b176 100644 --- a/spec/track-ids.js +++ b/spec/track-ids.js @@ -5,216 +5,184 @@ describe('track ids', function() { }); it('should not include anything without the flag', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}'); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids, undefined); - equal(options.hashIds, undefined); - - return 'success'; - } - }; - - equals(template({}, { helpers: helpers }), 'success'); + expectTemplate('{{wycats is.a slave.driver}}') + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids, undefined); + equal(options.hashIds, undefined); + + return 'success'; + } + }) + .toCompileTo('success'); }); - it('should include argument ids', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}', { - trackIds: true - }); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo slave.driver:bar' - ); + it('should include argument ids', function() { + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should include hash ids', function() { - var template = CompilerContext.compile( - '{{wycats bat=is.a baz=slave.driver}}', - { trackIds: true } - ); - - var helpers = { - wycats: function(options) { - equal(options.hashIds.bat, 'is.a'); - equal(options.hashIds.baz, 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.hashIds.bat + - ':' + - options.hash.bat + - ' ' + - options.hashIds.baz + - ':' + - options.hash.baz - ); - } - }; - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo slave.driver:bar' - ); + it('should include hash ids', function() { + expectTemplate('{{wycats bat=is.a baz=slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(options) { + equal(options.hashIds.bat, 'is.a'); + equal(options.hashIds.baz, 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.hashIds.bat + + ':' + + options.hash.bat + + ' ' + + options.hashIds.baz + + ':' + + options.hash.baz + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should note ../ and ./ references', function() { - var template = CompilerContext.compile( - '{{wycats ./is.a ../slave.driver this.is.a this}}', - { trackIds: true } - ); - var helpers = { - wycats: function(passiveVoice, noun, thiz, thiz2, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], '../slave.driver'); - equal(options.ids[2], 'is.a'); - equal(options.ids[3], ''); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined' - ); + it('should note ../ and ./ references', function() { + expectTemplate('{{wycats ./is.a ../slave.driver this.is.a this}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, thiz, thiz2, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], '../slave.driver'); + equal(options.ids[2], 'is.a'); + equal(options.ids[3], ''); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo ../slave.driver:undefined'); }); - it('should note @data references', function() { - var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', { - trackIds: true - }); - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], '@is.a'); - equal(options.ids[1], '@slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - equals( - template({}, { helpers: helpers, data: context }), - 'HELP ME MY BOSS @is.a:foo @slave.driver:bar' - ); + it('should note @data references', function() { + expectTemplate('{{wycats @is.a @slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], '@is.a'); + equal(options.ids[1], '@slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withRuntimeOptions({ data: context }) + .toCompileTo('HELP ME MY BOSS @is.a:foo @slave.driver:bar'); }); it('should return null for constants', function() { - var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', { - trackIds: true - }); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], null); - equal(options.ids[1], null); - equal(options.hashIds.key, null); - - return ( - 'HELP ME MY BOSS ' + - passiveVoice + - ' ' + - noun + - ' ' + - options.hash.key - ); - } - }; - - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS 1 foo false' - ); + expectTemplate('{{wycats 1 "foo" key=false}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], null); + equal(options.ids[1], null); + equal(options.hashIds.key, null); + + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ' ' + + options.hash.key + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS 1 foo false'); }); - it('should return true for subexpressions', function() { - var template = CompilerContext.compile('{{wycats (sub)}}', { - trackIds: true - }); - - var helpers = { - sub: function() { - return 1; - }, - wycats: function(passiveVoice, options) { - equal(options.ids[0], true); - - return 'HELP ME MY BOSS ' + passiveVoice; - } - }; - equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1'); + it('should return true for subexpressions', function() { + expectTemplate('{{wycats (sub)}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + sub: function() { + return 1; + }, + wycats: function(passiveVoice, options) { + equal(options.ids[0], true); + + return 'HELP ME MY BOSS ' + passiveVoice; + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS 1'); }); it('should use block param paths', function() { - var template = CompilerContext.compile( - '{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}', - { trackIds: true } - ); - - var helpers = { - doIt: function(options) { - var blockParams = [this.is]; - blockParams.path = ['zomg']; - return options.fn(this, { blockParams: blockParams }); - }, - wycats: function(passiveVoice, noun, blah, options) { - equal(options.ids[0], 'zomg.a'); - equal(options.ids[1], 'slave.driver'); - equal(options.ids[2], 'zomg'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS zomg.a:foo slave.driver:bar' - ); + expectTemplate('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + doIt: function(options) { + var blockParams = [this.is]; + blockParams.path = ['zomg']; + return options.fn(this, { blockParams: blockParams }); + }, + wycats: function(passiveVoice, noun, blah, options) { + equal(options.ids[0], 'zomg.a'); + equal(options.ids[1], 'slave.driver'); + equal(options.ids[2], 'zomg'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS zomg.a:foo slave.driver:bar'); }); describe('builtin helpers', function() { @@ -229,119 +197,85 @@ describe('track ids', function() { describe('#each', function() { it('should track contextPath for arrays', function() { - var template = CompilerContext.compile( - '{{#each array}}{{wycats name}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { array: [{ name: 'foo' }, { name: 'bar' }] }, - { helpers: helpers } - ), - 'foo:array.0\nbar:array.1\n' - ); + expectTemplate('{{#each array}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('foo:array.0\nbar:array.1\n'); }); + it('should track contextPath for keys', function() { - var template = CompilerContext.compile( - '{{#each object}}{{wycats name}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }, - { helpers: helpers } - ), - 'foo:object.foo\nbar:object.bar\n' - ); + expectTemplate('{{#each object}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }) + .toCompileTo('foo:object.foo\nbar:object.bar\n'); }); + it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { array: [{ name: 'foo' }, { name: 'bar' }] }, - { helpers: helpers } - ), - 'foo:.array..0\nbar:.array..1\n' - ); + expectTemplate( + '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('foo:.array..0\nbar:.array..1\n'); }); + it('should handle block params', function() { - var template = CompilerContext.compile( - '{{#each array as |value|}}{{blockParams value.name}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { array: [{ name: 'foo' }, { name: 'bar' }] }, - { helpers: helpers } - ), - 'foo:array.0.name\nbar:array.1.name\n' - ); + expectTemplate( + '{{#each array as |value|}}{{blockParams value.name}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('foo:array.0.name\nbar:array.1.name\n'); }); }); + describe('#with', function() { it('should track contextPath', function() { - var template = CompilerContext.compile( - '{{#with field}}{{wycats name}}{{/with}}', - { trackIds: true } - ); - - equals( - template({ field: { name: 'foo' } }, { helpers: helpers }), - 'foo:field\n' - ); + expectTemplate('{{#with field}}{{wycats name}}{{/with}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: { name: 'foo' } }) + .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', - { trackIds: true } - ); - equals( - template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }), - 'foo:bat.field\n' - ); + it('should handle nesting', function() { + expectTemplate( + '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ bat: { field: { name: 'foo' } } }) + .toCompileTo('foo:bat.field\n'); }); }); + describe('#blockHelperMissing', function() { it('should track contextPath for arrays', function() { - var template = CompilerContext.compile( - '{{#field}}{{wycats name}}{{/field}}', - { trackIds: true } - ); - - equals( - template({ field: [{ name: 'foo' }] }, { helpers: helpers }), - 'foo:field.0\n' - ); + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: [{ name: 'foo' }] }) + .toCompileTo('foo:field.0\n'); }); - it('should track contextPath for keys', function() { - var template = CompilerContext.compile( - '{{#field}}{{wycats name}}{{/field}}', - { trackIds: true } - ); - equals( - template({ field: { name: 'foo' } }, { helpers: helpers }), - 'foo:field\n' - ); + it('should track contextPath for keys', function() { + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: { name: 'foo' } }) + .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', - { trackIds: true } - ); - equals( - template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }), - 'foo:bat.field\n' - ); + it('should handle nesting', function() { + expectTemplate('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ bat: { field: { name: 'foo' } } }) + .toCompileTo('foo:bat.field\n'); }); }); }); diff --git a/spec/utils.js b/spec/utils.js index 667aa0ed3..f4a4e3e4f 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -15,11 +15,9 @@ describe('utils', function() { it('it should not escape SafeString properties', function() { var name = new Handlebars.SafeString('Sean O'Malley'); - shouldCompileTo( - '{{name}}', - [{ name: name }], - 'Sean O'Malley' - ); + expectTemplate('{{name}}') + .withInput({ name: name }) + .toCompileTo('Sean O'Malley'); }); }); diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index b1a6db045..f826d95a1 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -2,125 +2,156 @@ describe('whitespace control', function() { it('should strip whitespace around mustache calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo(' {{~foo~}} ', hash, 'bar<'); - shouldCompileTo(' {{~foo}} ', hash, 'bar< '); - shouldCompileTo(' {{foo~}} ', hash, ' bar<'); + expectTemplate(' {{~foo~}} ') + .withInput(hash) + .toCompileTo('bar<'); - shouldCompileTo(' {{~&foo~}} ', hash, 'bar<'); - shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<'); + expectTemplate(' {{~foo}} ') + .withInput(hash) + .toCompileTo('bar< '); - shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4'); + expectTemplate(' {{foo~}} ') + .withInput(hash) + .toCompileTo(' bar<'); + + expectTemplate(' {{~&foo~}} ') + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate(' {{~{foo}~}} ') + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate('1\n{{foo~}} \n\n 23\n{{bar}}4').toCompileTo('1\n23\n4'); }); describe('blocks', function() { it('should strip whitespace around simple block calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar'); - shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar '); - shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar '); - shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar '); + expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') + .withInput(hash) + .toCompileTo('bar'); - shouldCompileTo( - ' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', - hash, - 'bar' - ); - shouldCompileTo( - ' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', - hash, - ' abara ' - ); + expectTemplate(' {{#if foo~}} bar {{/if~}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' {{~#if foo}} bar {{~/if}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' {{#if foo}} bar {{/if}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ') + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ') + .withInput(hash) + .toCompileTo(' abara '); }); + it('should strip whitespace around inverse block calls', function() { - var hash = {}; + expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar'); - shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar'); - shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar '); - shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar '); - shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar '); + expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar '); - shouldCompileTo( - ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', - hash, - 'bar' - ); + expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar '); + + expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo(' bar '); + + expectTemplate( + ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ' + ).toCompileTo('bar'); }); + it('should strip whitespace around complex block calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar'); - shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar '); - shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar'); - shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar '); + expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo('bar'); - shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar'); + expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') + .withInput(hash) + .toCompileTo('bar '); - shouldCompileTo( - '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', - hash, - 'bar' - ); - shouldCompileTo( - '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', - hash, - 'bar<' + expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo(' bar'); + + expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate( + '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ) + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate( + '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ) + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo( + 'baz' ); - hash = {}; + expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz '); - shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz'); - shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz '); - shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz'); - shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz '); + expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo( + ' baz' + ); - shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz'); + expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo( + ' baz ' + ); - shouldCompileTo( - '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', - hash, + expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo( 'baz' ); + + expectTemplate( + '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ).toCompileTo('baz'); }); }); it('should strip whitespace around partials', function() { - shouldCompileToWithPartials( - 'foo {{~> dude~}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foobar' - ); - shouldCompileToWithPartials( - 'foo {{> dude~}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo bar' - ); - shouldCompileToWithPartials( - 'foo {{> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo bar ' - ); - - shouldCompileToWithPartials( - 'foo\n {{~> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foobar' - ); - shouldCompileToWithPartials( - 'foo\n {{> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo\n bar' - ); + expectTemplate('foo {{~> dude~}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foobar'); + + expectTemplate('foo {{> dude~}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo bar'); + + expectTemplate('foo {{> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo bar '); + + expectTemplate('foo\n {{~> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foobar'); + + expectTemplate('foo\n {{> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo\n bar'); }); it('should only strip whitespace once', function() { - var hash = { foo: 'bar' }; - - shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar '); + expectTemplate(' {{~foo~}} {{foo}} {{foo}} ') + .withInput({ foo: 'bar' }) + .toCompileTo('barbar bar '); }); });