Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
handlebars.js/spec/helpers.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1047 lines (947 sloc)
33 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe('helpers', function () { | |
it('helper with complex lookup$', function () { | |
expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}') | |
.withInput({ | |
prefix: '/root', | |
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], | |
}) | |
.withHelper('link', function (prefix) { | |
return ( | |
'<a href="' + prefix + '/' + this.url + '">' + this.text + '</a>' | |
); | |
}) | |
.toCompileTo('<a href="/root/goodbye">Goodbye</a>'); | |
}); | |
it('helper for raw block gets raw content', function () { | |
expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}') | |
.withInput({ test: 'hello' }) | |
.withHelper('raw', function (options) { | |
return options.fn(); | |
}) | |
.withMessage('raw block helper gets raw content') | |
.toCompileTo(' {{test}} '); | |
}); | |
it('helper for raw block gets parameters', function () { | |
expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}') | |
.withInput({ test: 'hello' }) | |
.withHelper('raw', function (a, b, c, options) { | |
return options.fn() + a + b + c; | |
}) | |
.withMessage('raw block helper gets raw content') | |
.toCompileTo(' {{test}} 123'); | |
}); | |
describe('raw block parsing (with identity helper-function)', function () { | |
function runWithIdentityHelper(template, expected) { | |
expectTemplate(template) | |
.withHelper('identity', function (options) { | |
return options.fn(); | |
}) | |
.toCompileTo(expected); | |
} | |
it('helper for nested raw block gets raw content', function () { | |
runWithIdentityHelper( | |
'{{{{identity}}}} {{{{b}}}} {{{{/b}}}} {{{{/identity}}}}', | |
' {{{{b}}}} {{{{/b}}}} ' | |
); | |
}); | |
it('helper for nested raw block works with empty content', function () { | |
runWithIdentityHelper('{{{{identity}}}}{{{{/identity}}}}', ''); | |
}); | |
xit('helper for nested raw block works if nested raw blocks are broken', function () { | |
// This test was introduced in 4.4.4, but it was not the actual problem that lead to the patch release | |
// The test is deactivated, because in 3.x this template cases an exception and it also does not work in 4.4.3 | |
// If anyone can make this template work without breaking everything else, then go for it, | |
// but for now, this is just a known bug, that will be documented. | |
runWithIdentityHelper( | |
'{{{{identity}}}} {{{{a}}}} {{{{ {{{{/ }}}} }}}} {{{{/identity}}}}', | |
' {{{{a}}}} {{{{ {{{{/ }}}} }}}} ' | |
); | |
}); | |
it('helper for nested raw block closes after first matching close', function () { | |
runWithIdentityHelper( | |
'{{{{identity}}}}abc{{{{/identity}}}} {{{{identity}}}}abc{{{{/identity}}}}', | |
'abc abc' | |
); | |
}); | |
it('helper for nested raw block throw exception when with missing closing braces', function () { | |
var string = '{{{{a}}}} {{{{/a'; | |
expectTemplate(string).toThrow(); | |
}); | |
}); | |
it('helper block with identical context', function () { | |
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; | |
}) | |
.toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); | |
}); | |
it('helper block with complex lookup expression', function () { | |
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; | |
}) | |
.toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); | |
}); | |
it('helper with complex lookup and nested template', function () { | |
expectTemplate( | |
'{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' | |
) | |
.withInput({ | |
prefix: '/root', | |
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], | |
}) | |
.withHelper('link', function (prefix, options) { | |
return ( | |
'<a href="' + | |
prefix + | |
'/' + | |
this.url + | |
'">' + | |
options.fn(this) + | |
'</a>' | |
); | |
}) | |
.toCompileTo('<a href="/root/goodbye">Goodbye</a>'); | |
}); | |
it('helper with complex lookup and nested template in VM+Compiler', function () { | |
expectTemplate( | |
'{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' | |
) | |
.withInput({ | |
prefix: '/root', | |
goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], | |
}) | |
.withHelper('link', function (prefix, options) { | |
return ( | |
'<a href="' + | |
prefix + | |
'/' + | |
this.url + | |
'">' + | |
options.fn(this) + | |
'</a>' | |
); | |
}) | |
.toCompileTo('<a href="/root/goodbye">Goodbye</a>'); | |
}); | |
it('helper returning undefined value', function () { | |
expectTemplate(' {{nothere}}') | |
.withHelpers({ | |
nothere: function () {}, | |
}) | |
.toCompileTo(' '); | |
expectTemplate(' {{#nothere}}{{/nothere}}') | |
.withHelpers({ | |
nothere: function () {}, | |
}) | |
.toCompileTo(' '); | |
}); | |
it('block helper', function () { | |
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 () { | |
expectTemplate('{{#form}}<p>{{name}}</p>{{/form}}') | |
.withInput({ name: 'Yehuda' }) | |
.withHelper('form', function (options) { | |
return '<form>' + options.fn(this) + '</form>'; | |
}) | |
.withMessage('Block helper executed with current context') | |
.toCompileTo('<form><p>Yehuda</p></form>'); | |
}); | |
it('block helper should have context in this', function () { | |
function link(options) { | |
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>'; | |
} | |
expectTemplate( | |
'<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>' | |
) | |
.withInput({ | |
people: [ | |
{ name: 'Alan', id: 1 }, | |
{ name: 'Yehuda', id: 2 }, | |
], | |
}) | |
.withHelper('link', link) | |
.toCompileTo( | |
'<ul><li><a href="/people/1">Alan</a></li><li><a href="/people/2">Yehuda</a></li></ul>' | |
); | |
}); | |
it('block helper for undefined value', function () { | |
expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo(''); | |
}); | |
it('block helper passing a new context', function () { | |
expectTemplate('{{#form yehuda}}<p>{{name}}</p>{{/form}}') | |
.withInput({ yehuda: { name: 'Yehuda' } }) | |
.withHelper('form', function (context, options) { | |
return '<form>' + options.fn(context) + '</form>'; | |
}) | |
.withMessage('Context variable resolved') | |
.toCompileTo('<form><p>Yehuda</p></form>'); | |
}); | |
it('block helper passing a complex path context', function () { | |
expectTemplate('{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}') | |
.withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }) | |
.withHelper('form', function (context, options) { | |
return '<form>' + options.fn(context) + '</form>'; | |
}) | |
.withMessage('Complex path variable resolved') | |
.toCompileTo('<form><p>Harold</p></form>'); | |
}); | |
it('nested block helpers', function () { | |
expectTemplate( | |
'{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}' | |
) | |
.withInput({ | |
yehuda: { name: 'Yehuda' }, | |
}) | |
.withHelper('link', function (options) { | |
return '<a href="' + this.name + '">' + options.fn(this) + '</a>'; | |
}) | |
.withHelper('form', function (context, options) { | |
return '<form>' + options.fn(context) + '</form>'; | |
}) | |
.withMessage('Both blocks executed') | |
.toCompileTo('<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>'); | |
}); | |
it('block helper inverted sections', function () { | |
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"; | |
function list(context, options) { | |
if (context.length > 0) { | |
var out = '<ul>'; | |
for (var i = 0, j = context.length; i < j; i++) { | |
out += '<li>'; | |
out += options.fn(context[i]); | |
out += '</li>'; | |
} | |
out += '</ul>'; | |
return out; | |
} else { | |
return '<p>' + options.inverse(this) + '</p>'; | |
} | |
} | |
// the meaning here may be kind of hard to catch, but list.not is always called, | |
// so we should see the output of both | |
expectTemplate(string) | |
.withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] }) | |
.withHelpers({ list: list }) | |
.withMessage('an inverse wrapper is passed in as a new context') | |
.toCompileTo('<ul><li>Alan</li><li>Yehuda</li></ul>'); | |
expectTemplate(string) | |
.withInput({ people: [] }) | |
.withHelpers({ list: list }) | |
.withMessage('an inverse wrapper can be optionally called') | |
.toCompileTo("<p><em>Nobody's here</em></p>"); | |
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('<p>Nobody's here</p>'); | |
}); | |
it('pathed lambas with parameters', function () { | |
var hash = { | |
helper: function () { | |
return 'winning'; | |
}, | |
}; | |
hash.hash = hash; | |
var helpers = { | |
'./helper': function () { | |
return 'fail'; | |
}, | |
}; | |
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 () { | |
expectTemplate('Goodbye {{cruel}} {{world}}!') | |
.withInput({ cruel: 'cruel' }) | |
.withHelpers({ | |
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'; | |
}, | |
}) | |
.withMessage('helpers hash is available inside other blocks') | |
.toCompileTo('Goodbye cruel world!'); | |
}); | |
it('in cases of conflict, helpers win', function () { | |
expectTemplate('{{{lookup}}}') | |
.withInput({ lookup: 'Explicit' }) | |
.withHelpers({ | |
lookup: function () { | |
return 'helpers'; | |
}, | |
}) | |
.withMessage('helpers hash has precedence escaped expansion') | |
.toCompileTo('helpers'); | |
expectTemplate('{{lookup}}') | |
.withInput({ lookup: 'Explicit' }) | |
.withHelpers({ | |
lookup: function () { | |
return 'helpers'; | |
}, | |
}) | |
.withMessage('helpers hash has precedence simple expansion') | |
.toCompileTo('helpers'); | |
}); | |
it('the helpers hash is available is nested contexts', function () { | |
expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}') | |
.withInput({ outer: { inner: { unused: [] } } }) | |
.withHelpers({ | |
helper: function () { | |
return 'helper'; | |
}, | |
}) | |
.withMessage('helpers hash is available in nested contexts.') | |
.toCompileTo('helper'); | |
}); | |
it('the helper hash should augment the global hash', function () { | |
handlebarsEnv.registerHelper('test_helper', function () { | |
return 'found it!'; | |
}); | |
expectTemplate( | |
'{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' | |
) | |
.withInput({ cruel: 'cruel' }) | |
.withHelpers({ | |
world: function () { | |
return 'world!'; | |
}, | |
}) | |
.toCompileTo('found it! Goodbye cruel world!!'); | |
}); | |
}); | |
describe('registration', function () { | |
it('unregisters', function () { | |
handlebarsEnv.helpers = {}; | |
handlebarsEnv.registerHelper('foo', function () { | |
return 'fail'; | |
}); | |
handlebarsEnv.unregisterHelper('foo'); | |
equals(handlebarsEnv.helpers.foo, undefined); | |
}); | |
it('allows multiple globals', function () { | |
var helpers = handlebarsEnv.helpers; | |
handlebarsEnv.helpers = {}; | |
handlebarsEnv.registerHelper({ | |
if: helpers['if'], | |
world: function () { | |
return 'world!'; | |
}, | |
testHelper: function () { | |
return 'found it!'; | |
}, | |
}); | |
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 () { | |
handlebarsEnv.registerHelper( | |
{ | |
world: function () { | |
return 'world!'; | |
}, | |
testHelper: function () { | |
return 'found it!'; | |
}, | |
}, | |
{} | |
); | |
}, | |
Error, | |
'Arg not supported with multiple helpers' | |
); | |
}); | |
}); | |
it('decimal number literals work', function () { | |
expectTemplate('Message: {{hello -1.2 1.2}}') | |
.withHelper('hello', function (times, times2) { | |
if (typeof times !== 'number') { | |
times = 'NaN'; | |
} | |
if (typeof times2 !== 'number') { | |
times2 = 'NaN'; | |
} | |
return 'Hello ' + times + ' ' + times2 + ' times'; | |
}) | |
.withMessage('template with a negative integer literal') | |
.toCompileTo('Message: Hello -1.2 1.2 times'); | |
}); | |
it('negative number literals work', function () { | |
expectTemplate('Message: {{hello -12}}') | |
.withHelper('hello', function (times) { | |
if (typeof times !== 'number') { | |
times = 'NaN'; | |
} | |
return 'Hello ' + times + ' times'; | |
}) | |
.withMessage('template with a negative integer literal') | |
.toCompileTo('Message: Hello -12 times'); | |
}); | |
describe('String literal parameters', function () { | |
it('simple literals work', function () { | |
expectTemplate('Message: {{hello "world" 12 true false}}') | |
.withHelper('hello', function (param, times, bool1, bool2) { | |
if (typeof times !== 'number') { | |
times = 'NaN'; | |
} | |
if (typeof bool1 !== 'boolean') { | |
bool1 = 'NaB'; | |
} | |
if (typeof bool2 !== 'boolean') { | |
bool2 = 'NaB'; | |
} | |
return ( | |
'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 | |
); | |
}) | |
.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 () { | |
expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error); | |
}); | |
it('escaping a String is possible', function () { | |
expectTemplate('Message: {{{hello "\\"world\\""}}}') | |
.withHelper('hello', function (param) { | |
return 'Hello ' + param; | |
}) | |
.withMessage('template with an escaped String literal') | |
.toCompileTo('Message: Hello "world"'); | |
}); | |
it("it works with ' marks", function () { | |
expectTemplate('Message: {{{hello "Alan\'s world"}}}') | |
.withHelper('hello', function (param) { | |
return 'Hello ' + param; | |
}) | |
.withMessage("template with a ' mark") | |
.toCompileTo("Message: Hello Alan's world"); | |
}); | |
}); | |
it('negative number literals work', function () { | |
expectTemplate('Message: {{hello -12}}') | |
.withHelper('hello', function (times) { | |
if (typeof times !== 'number') { | |
times = 'NaN'; | |
} | |
return 'Hello ' + times + ' times'; | |
}) | |
.withMessage('template with a negative integer literal') | |
.toCompileTo('Message: Hello -12 times'); | |
}); | |
describe('multiple parameters', function () { | |
it('simple multi-params work', function () { | |
expectTemplate('Message: {{goodbye cruel world}}') | |
.withInput({ cruel: 'cruel', world: 'world' }) | |
.withHelper('goodbye', function (cruel, world) { | |
return 'Goodbye ' + cruel + ' ' + world; | |
}) | |
.withMessage('regular helpers with multiple params') | |
.toCompileTo('Message: Goodbye cruel world'); | |
}); | |
it('block multi-params work', function () { | |
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 }); | |
}) | |
.withMessage('block helpers with multiple params') | |
.toCompileTo('Message: Goodbye cruel world'); | |
}); | |
}); | |
describe('hash', function () { | |
it('helpers can take an optional hash', function () { | |
expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}') | |
.withHelper('goodbye', function (options) { | |
return ( | |
'GOODBYE ' + | |
options.hash.cruel + | |
' ' + | |
options.hash.world + | |
' ' + | |
options.hash.times + | |
' TIMES' | |
); | |
}) | |
.withMessage('Helper output hash') | |
.toCompileTo('GOODBYE CRUEL WORLD 12 TIMES'); | |
}); | |
it('helpers can take an optional hash with booleans', function () { | |
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'; | |
} | |
} | |
expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}') | |
.withHelper('goodbye', goodbye) | |
.withMessage('Helper output hash') | |
.toCompileTo('GOODBYE CRUEL WORLD'); | |
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 () { | |
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') | |
.withHelper('goodbye', function (options) { | |
return ( | |
'GOODBYE ' + | |
options.hash.cruel + | |
' ' + | |
options.fn(this) + | |
' ' + | |
options.hash.times + | |
' TIMES' | |
); | |
}) | |
.withMessage('Hash parameters output') | |
.toCompileTo('GOODBYE CRUEL world 12 TIMES'); | |
}); | |
it('block helpers can take an optional hash with single quoted stings', function () { | |
expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') | |
.withHelper('goodbye', function (options) { | |
return ( | |
'GOODBYE ' + | |
options.hash.cruel + | |
' ' + | |
options.fn(this) + | |
' ' + | |
options.hash.times + | |
' TIMES' | |
); | |
}) | |
.withMessage('Hash parameters output') | |
.toCompileTo('GOODBYE CRUEL world 12 TIMES'); | |
}); | |
it('block helpers can take an optional hash with booleans', function () { | |
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'; | |
} | |
} | |
expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}') | |
.withHelper('goodbye', goodbye) | |
.withMessage('Boolean hash parameter honored') | |
.toCompileTo('GOODBYE CRUEL world'); | |
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 () { | |
expectTemplate('{{hello}} {{link_to world}}').toThrow( | |
/Missing helper: "link_to"/ | |
); | |
}); | |
it('if a context is not found, custom helperMissing is used', function () { | |
expectTemplate('{{hello}} {{link_to world}}') | |
.withInput({ hello: 'Hello', world: 'world' }) | |
.withHelper('helperMissing', function (mesg, options) { | |
if (options.name === 'link_to') { | |
return new Handlebars.SafeString('<a>' + mesg + '</a>'); | |
} | |
}) | |
.toCompileTo('Hello <a>world</a>'); | |
}); | |
it('if a value is not found, custom helperMissing is used', function () { | |
expectTemplate('{{hello}} {{link_to}}') | |
.withInput({ hello: 'Hello', world: 'world' }) | |
.withHelper('helperMissing', function (options) { | |
if (options.name === 'link_to') { | |
return new Handlebars.SafeString('<a>winning</a>'); | |
} | |
}) | |
.toCompileTo('Hello <a>winning</a>'); | |
}); | |
}); | |
describe('knownHelpers', function () { | |
it('Known helper should render helper', function () { | |
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 () { | |
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 () { | |
expectTemplate('{{#unless foo}}bar{{/unless}}') | |
.withCompileOptions({ | |
knownHelpersOnly: true, | |
}) | |
.toCompileTo('bar'); | |
}); | |
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 () { | |
expectTemplate('{{#foo}}bar{{/foo}}') | |
.withCompileOptions({ | |
knownHelpersOnly: true, | |
}) | |
.withInput({ foo: 'baz' }) | |
.toCompileTo('bar'); | |
}); | |
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 () { | |
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 () { | |
expectTemplate('{{typeof hello}}') | |
.withCompileOptions({ knownHelpersOnly: true }) | |
.toThrow(Error); | |
}); | |
}); | |
describe('blockHelperMissing', function () { | |
it('lambdas are resolved by blockHelperMissing, not handlebars proper', function () { | |
expectTemplate('{{#truthy}}yep{{/truthy}}') | |
.withInput({ | |
truthy: function () { | |
return true; | |
}, | |
}) | |
.toCompileTo('yep'); | |
}); | |
it('lambdas resolved by blockHelperMissing are bound to the context', function () { | |
expectTemplate('{{#truthy}}yep{{/truthy}}') | |
.withInput({ | |
truthy: function () { | |
return this.truthiness(); | |
}, | |
truthiness: function () { | |
return false; | |
}, | |
}) | |
.toCompileTo(''); | |
}); | |
}); | |
describe('name field', function () { | |
var helpers = { | |
blockHelperMissing: function () { | |
return 'missing: ' + arguments[arguments.length - 1].name; | |
}, | |
helperMissing: function () { | |
return 'helper missing: ' + arguments[arguments.length - 1].name; | |
}, | |
helper: function () { | |
return 'ran: ' + arguments[arguments.length - 1].name; | |
}, | |
}; | |
it('should include in ambiguous mustache calls', function () { | |
expectTemplate('{{helper}}') | |
.withHelpers(helpers) | |
.toCompileTo('ran: helper'); | |
}); | |
it('should include in helper mustache calls', function () { | |
expectTemplate('{{helper 1}}') | |
.withHelpers(helpers) | |
.toCompileTo('ran: helper'); | |
}); | |
it('should include in ambiguous block calls', function () { | |
expectTemplate('{{#helper}}{{/helper}}') | |
.withHelpers(helpers) | |
.toCompileTo('ran: helper'); | |
}); | |
it('should include in simple block calls', function () { | |
expectTemplate('{{#./helper}}{{/./helper}}') | |
.withHelpers(helpers) | |
.toCompileTo('missing: ./helper'); | |
}); | |
it('should include in helper block calls', function () { | |
expectTemplate('{{#helper 1}}{{/helper}}') | |
.withHelpers(helpers) | |
.toCompileTo('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 () { | |
expectTemplate('{{#foo.helper}}{{/foo.helper}}') | |
.withInput({ foo: {} }) | |
.withHelpers(helpers) | |
.toCompileTo('missing: foo.helper'); | |
}); | |
it('should include full id if a hash is passed', function () { | |
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 () { | |
expectTemplate('{{goodbye}} {{cruel world}}') | |
.withHelper('goodbye', function () { | |
return this.goodbye.toUpperCase(); | |
}) | |
.withHelper('cruel', function (world) { | |
return 'cruel ' + world.toUpperCase(); | |
}) | |
.withInput({ | |
goodbye: 'goodbye', | |
world: 'world', | |
}) | |
.withMessage('Helper executed') | |
.toCompileTo('GOODBYE cruel WORLD'); | |
}); | |
it('helpers take precedence over same-named context properties$', function () { | |
expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}') | |
.withHelper('goodbye', function (options) { | |
return this.goodbye.toUpperCase() + options.fn(this); | |
}) | |
.withHelper('cruel', function (world) { | |
return 'cruel ' + world.toUpperCase(); | |
}) | |
.withInput({ | |
goodbye: 'goodbye', | |
world: 'world', | |
}) | |
.withMessage('Helper executed') | |
.toCompileTo('GOODBYE cruel WORLD'); | |
}); | |
it('Scoped names take precedence over helpers', function () { | |
expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}') | |
.withHelper('goodbye', function () { | |
return this.goodbye.toUpperCase(); | |
}) | |
.withHelper('cruel', function (world) { | |
return 'cruel ' + world.toUpperCase(); | |
}) | |
.withInput({ | |
goodbye: 'goodbye', | |
world: 'world', | |
}) | |
.withMessage('Helper not executed') | |
.toCompileTo('goodbye cruel WORLD cruel GOODBYE'); | |
}); | |
it('Scoped names take precedence over block helpers', function () { | |
expectTemplate( | |
'{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' | |
) | |
.withHelper('goodbye', function (options) { | |
return this.goodbye.toUpperCase() + options.fn(this); | |
}) | |
.withHelper('cruel', function (world) { | |
return 'cruel ' + world.toUpperCase(); | |
}) | |
.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 () { | |
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] }); | |
}) | |
.toCompileTo('1foo'); | |
}); | |
it('should take presedence over helper values', function () { | |
expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') | |
.withHelper('value', function () { | |
return 'foo'; | |
}) | |
.withHelper('goodbyes', function (options) { | |
equals(options.fn.blockParams, 1); | |
return options.fn({}, { blockParams: [1, 2] }); | |
}) | |
.toCompileTo('1foo'); | |
}); | |
it('should not take presedence over pathed values', function () { | |
expectTemplate( | |
'{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' | |
) | |
.withInput({ value: 'bar' }) | |
.withHelper('value', function () { | |
return 'foo'; | |
}) | |
.withHelper('goodbyes', function (options) { | |
equals(options.fn.blockParams, 1); | |
return options.fn(this, { blockParams: [1, 2] }); | |
}) | |
.toCompileTo('barfoo'); | |
}); | |
it('should take presednece over parent block params', function () { | |
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' }, | |
{ | |
blockParams: | |
options.fn.blockParams === 1 ? [value++, value++] : undefined, | |
} | |
); | |
}) | |
.toCompileTo('13foo'); | |
}); | |
it('should allow block params on chained helpers', function () { | |
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] }); | |
}) | |
.toCompileTo('1foo'); | |
}); | |
}); | |
describe('built-in helpers malformed arguments ', function () { | |
it('if helper - too few arguments', function () { | |
expectTemplate('{{#if}}{{/if}}').toThrow( | |
/#if requires exactly one argument/ | |
); | |
}); | |
it('if helper - too many arguments, string', function () { | |
expectTemplate('{{#if test "string"}}{{/if}}').toThrow( | |
/#if requires exactly one argument/ | |
); | |
}); | |
it('if helper - too many arguments, undefined', function () { | |
expectTemplate('{{#if test undefined}}{{/if}}').toThrow( | |
/#if requires exactly one argument/ | |
); | |
}); | |
it('if helper - too many arguments, null', function () { | |
expectTemplate('{{#if test null}}{{/if}}').toThrow( | |
/#if requires exactly one argument/ | |
); | |
}); | |
it('unless helper - too few arguments', function () { | |
expectTemplate('{{#unless}}{{/unless}}').toThrow( | |
/#unless requires exactly one argument/ | |
); | |
}); | |
it('unless helper - too many arguments', function () { | |
expectTemplate('{{#unless test null}}{{/unless}}').toThrow( | |
/#unless requires exactly one argument/ | |
); | |
}); | |
it('with helper - too few arguments', function () { | |
expectTemplate('{{#with}}{{/with}}').toThrow( | |
/#with requires exactly one argument/ | |
); | |
}); | |
it('with helper - too many arguments', function () { | |
expectTemplate('{{#with test "string"}}{{/with}}').toThrow( | |
/#with requires exactly one argument/ | |
); | |
}); | |
}); | |
describe('the lookupProperty-option', function () { | |
it('should be passed to custom helpers', function () { | |
expectTemplate('{{testHelper}}') | |
.withHelper('testHelper', function testHelper(options) { | |
return options.lookupProperty(this, 'testProperty'); | |
}) | |
.withInput({ testProperty: 'abc' }) | |
.toCompileTo('abc'); | |
}); | |
}); | |
}); |