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