410141c31e
The ‘ character would cause invalid javascript to be generated as it was not properly escaped. Switching to JSON.stringify safely handles all potential unescaped cases.
306 lines
13 KiB
JavaScript
306 lines
13 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');
|
|
});
|
|
});
|