style: reformat all files using prettier

This commit is contained in:
Nils Knappmeier
2019-12-03 22:37:15 +01:00
parent e913dc5f12
commit e97685e989
93 changed files with 6205 additions and 2360 deletions
+158 -78
View File
@@ -7,44 +7,102 @@ describe('ast', function() {
describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
shouldThrow(
function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
},
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
});
});
describe('helpers', function() {
describe('#helperExpression', function() {
it('should handle mustache statements', function() {
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle block statements', function() {
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle subexpressions', function() {
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
});
it('should work with non-helper nodes', function() {
equals(AST.helpers.helperExpression({type: 'Program'}), false);
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
equals(
AST.helpers.helperExpression({ type: 'PartialStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'ContentStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'CommentStatement' }),
false
);
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
equals(
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
false
);
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'Hash'}), false);
equals(AST.helpers.helperExpression({type: 'HashPair'}), false);
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
});
});
});
@@ -61,17 +119,18 @@ describe('ast', function() {
/* eslint-disable no-multi-spaces */
ast = Handlebars.parse(
'line 1 {{line1Token}}\n' // 1
+ ' line 2 {{line2token}}\n' // 2
+ ' line 3 {{#blockHelperOnLine3}}\n' // 3
+ 'line 4{{line4token}}\n' // 4
+ 'line5{{else}}\n' // 5
+ '{{line6Token}}\n' // 6
+ '{{/blockHelperOnLine3}}\n' // 7
+ '{{#open}}\n' // 8
+ '{{else inverse}}\n' // 9
+ '{{else}}\n' // 10
+ '{{/open}}'); // 11
'line 1 {{line1Token}}\n' + // 1
' line 2 {{line2token}}\n' + // 2
' line 3 {{#blockHelperOnLine3}}\n' + // 3
'line 4{{line4token}}\n' + // 4
'line5{{else}}\n' + // 5
'{{line6Token}}\n' + // 6
'{{/blockHelperOnLine3}}\n' + // 7
'{{#open}}\n' + // 8
'{{else inverse}}\n' + // 9
'{{else}}\n' + // 10
'{{/open}}'
); // 11
/* eslint-enable no-multi-spaces */
body = ast.body;
@@ -92,35 +151,35 @@ describe('ast', function() {
it('gets MustacheStatement line numbers correct across newlines', function() {
var secondMustacheStatement = body[3];
testColumns(secondMustacheStatement, 2, 2, 8, 22);
});
});
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers the program of a block helper', function() {
var blockHelperNode = body[5],
program = blockHelperNode.program;
it('correctly records the line numbers the program of a block helper', function() {
var blockHelperNode = body[5],
program = blockHelperNode.program;
testColumns(program, 3, 5, 31, 5);
});
testColumns(program, 3, 5, 31, 5);
});
it('correctly records the line numbers of an inverse of a block helper', function() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
it('correctly records the line numbers of an inverse of a block helper', function() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 7, 13, 0);
});
testColumns(inverse, 5, 7, 13, 0);
});
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
});
describe('whitespace control', function() {
@@ -149,7 +208,9 @@ describe('ast', function() {
});
it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment~}} \nfoo\n {{~/comment}}');
var ast = Handlebars.parseWithoutProcessing(
' {{# comment~}} \nfoo\n {{~/comment}}'
);
equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
@@ -167,7 +228,9 @@ describe('ast', function() {
});
describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
var ast = Handlebars.parseWithoutProcessing(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, ' ');
@@ -178,13 +241,17 @@ describe('ast', function() {
equals(ast.body[2].value, ' ');
});
it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \nfoo\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n ');
});
it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \n{{foo}}\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \n');
@@ -192,7 +259,9 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n ');
});
it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
@@ -202,8 +271,10 @@ describe('ast', function() {
equals(block.inverse.body[0].value, ' \n bar \n ');
});
it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parseWithoutProcessing(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
@@ -215,8 +286,10 @@ describe('ast', function() {
});
describe('blocks', function() {
it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parse(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, '');
@@ -227,22 +300,24 @@ describe('ast', function() {
});
it('marks initial block mustaches as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
block = ast.body[0];
block = ast.body[0];
equals(block.program.body[0].value, 'foo\n');
});
it('marks mustaches with children as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
block = ast.body[0];
block = ast.body[0];
equals(block.program.body[0].value, '');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n');
});
it('marks nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
var ast = Handlebars.parse(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, '');
@@ -252,9 +327,11 @@ describe('ast', function() {
equals(body[0].value, '');
});
it('does not mark nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
var ast = Handlebars.parse(
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].omit, undefined);
@@ -264,9 +341,11 @@ describe('ast', function() {
equals(body[0].omit, undefined);
});
it('does not mark nested initial block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
body = ast.body[0].program.body,
block = body[0];
var ast = Handlebars.parse(
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
),
body = ast.body[0].program.body,
block = body[0];
equals(block.program.body[0].value, ' \nfoo\n');
equals(block.inverse.body[0].value, ' bar \n ');
@@ -275,8 +354,10 @@ describe('ast', function() {
});
it('marks column 0 block mustaches as standalone', function() {
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parse(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
@@ -348,4 +429,3 @@ describe('ast', function() {
});
});
});
+442 -139
View File
@@ -18,8 +18,12 @@ describe('basic context', function() {
});
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');
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() {
@@ -27,15 +31,26 @@ describe('basic context', function() {
});
it('compiling with an undefined context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
shouldCompileTo(
'Goodbye\n{{cruel}}\n{{world.bar}}!',
undefined,
'Goodbye\n\n!'
);
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
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(
'{{! 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');
@@ -47,237 +62,506 @@ describe('basic context', function() {
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: 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');
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(
'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');
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(
'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(
'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');
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!');
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");
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');
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 3 handlebars aren't escaped"
);
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with {{& handlebars aren't escaped");
shouldCompileTo(
'{{&awesome}}',
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with {{& handlebars aren't escaped"
);
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped');
shouldCompileTo(
'{{awesome}}',
{ awesome: '&"\'`\\<>' },
'&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped'
);
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: &lt;b&gt;'}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'escaping should properly handle amperstands');
shouldCompileTo(
'{{awesome}}',
{ awesome: 'Escaped, <b> looks like: &lt;b&gt;' },
'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;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");
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');
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');
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');
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');
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');
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');
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');
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');
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');
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 (-)');
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');
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');
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');
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!');
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: ');
shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: ');
});
it('complex but empty paths', function() {
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
shouldCompileTo('{{person/name}}', {person: {}}, '');
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');
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');
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');
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');
shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar');
shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar');
});
it('this keyword in helpers', function() {
var helpers = {foo: function(value) {
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');
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');
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');
shouldThrow(
function() {
CompilerContext.compile(string);
},
Error,
'Invalid path: text/this - 1:17'
);
shouldCompileTo(
'{{foo [this]}}',
{foo: function(value) { return value; }, 'this': 'bar'},
'bar');
'{{foo [this]}}',
{
foo: function(value) {
return value;
},
this: 'bar'
},
'bar'
);
shouldCompileTo(
'{{foo text/[this]}}',
{foo: function(value) { return value; }, text: {'this': 'bar'}},
'bar');
'{{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');
shouldCompileTo(
'{{#"foo"}}{{.}}{{/"foo"}}',
{ foo: ['bar', 'baz'] },
'barbaz'
);
});
it('pass number literals', function() {
@@ -285,13 +569,21 @@ describe('basic context', function() {
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');
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');
shouldCompileTo('{{false}}', { false: 'foo' }, 'foo');
});
it('should handle literals in subexpression', function() {
@@ -300,6 +592,17 @@ describe('basic context', function() {
return arg;
}
};
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
shouldCompileTo(
'{{foo (false)}}',
[
{
false: function() {
return 'bar';
}
},
helpers
],
'bar'
);
});
});
+255 -98
View File
@@ -1,51 +1,99 @@
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');
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');
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
});
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');
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'
);
});
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 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');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
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');
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');
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
});
it('block with complex lookup', function() {
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
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');
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'
);
});
it('multiple blocks with complex lookup', function() {
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
var hash = {
name: 'Alan',
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
};
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
});
@@ -59,111 +107,201 @@ describe('blocks', function() {
});
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' }] }] };
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!');
});
it('works with cached blocks', function() {
var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
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'}]});
var result = template({
person: [
{ first: 'Alan', last: 'Johnson' },
{ first: 'Alan', last: 'Johnson' }
]
});
equals(result, 'Alan JohnsonAlan Johnson');
});
describe('inverted sections', function() {
it('inverted sections with unset value', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var string =
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {};
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
shouldCompileTo(
string,
hash,
'Right On!',
"Inverted section rendered when value isn't set."
);
});
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.');
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.'
);
});
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.');
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.'
);
});
it('block inverted sections', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people' },
'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');
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'
);
});
it('chained inverted sections with mismatch', function() {
shouldThrow(function() {
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
{ none: 'No people' },
'No people'
);
}, Error);
});
it('block inverted sections with empty arrays', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people', people: [] },
'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');
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'
);
});
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');
[{ 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');
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
'\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');
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'
);
});
it('should handle nesting', function() {
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
shouldCompileTo(
'{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
{ data: [1, 3, 5] },
'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' }] }] };
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!');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'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' }] }] };
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!');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'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' }] }] };
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 ');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'Goodbye cruel '
);
});
});
@@ -181,9 +319,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply allow undefined return', function() {
var helpers = {
@@ -197,9 +336,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}suc{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator}}suc{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply block decorators', function() {
@@ -215,9 +355,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should support nested decorators', function() {
var helpers = {
@@ -235,9 +376,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply multiple decorators', function() {
@@ -253,9 +395,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should access parent variables', function() {
@@ -271,9 +414,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator foo}}{{/helper}}',
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator foo}}{{/helper}}',
{ hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
'success'
);
});
it('should work with root program', function() {
var run;
@@ -285,9 +429,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{*decorator "success"}}',
{hash: {'foo': 'success'}, decorators: decorators},
'');
'{{*decorator "success"}}',
{ hash: { foo: 'success' }, decorators: decorators },
''
);
equals(run, true);
});
it('should fail when accessing variables from root', function() {
@@ -300,9 +445,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{*decorator foo}}',
{hash: {'foo': 'fail'}, decorators: decorators},
'');
'{{*decorator foo}}',
{ hash: { foo: 'fail' }, decorators: decorators },
''
);
equals(run, true);
});
@@ -335,12 +481,23 @@ describe('blocks', function() {
equals(handlebarsEnv.decorators.bar, undefined);
});
it('fails with multiple and args', function() {
shouldThrow(function() {
handlebarsEnv.registerDecorator({
world: function() { return 'world!'; },
testHelper: function() { return 'found it!'; }
}, {});
}, Error, 'Arg not supported with multiple decorators');
shouldThrow(
function() {
handlebarsEnv.registerDecorator(
{
world: function() {
return 'world!';
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
});
});
});
+372 -109
View File
@@ -2,64 +2,156 @@ 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');
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'
);
});
it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns true');
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns string');
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns false');
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns undefined');
shouldCompileTo(
string,
{
goodbye: function() {
return true;
},
world: 'world'
},
'GOODBYE cruel world!',
'if with function shows the contents when function returns true'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.world;
},
world: 'world'
},
'GOODBYE cruel world!',
'if with function shows the contents when function returns string'
);
shouldCompileTo(
string,
{
goodbye: function() {
return false;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns false'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.foo;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns undefined'
);
});
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!');
var string =
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
shouldCompileTo(
string,
{ foo: { goodbye: true }, world: 'world' },
'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');
shouldCompileTo(
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
});
it('with with function argument', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
shouldCompileTo(
string,
{
person: function() {
return { first: 'Alan', last: 'Johnson' };
}
},
'Alan Johnson'
);
});
it('with with else', function() {
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
var string =
'{{#with person}}Person is present{{else}}Person is not present{{/with}}';
shouldCompileTo(string, {}, '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');
shouldCompileTo(
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
});
it('works when data is disabled', function() {
var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
var template = CompilerContext.compile(
'{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}',
{ data: false }
);
var result = template({person: {first: 'Alan', last: 'Johnson'}});
var result = template({ person: { first: 'Alan', last: 'Johnson' } });
equals(result, 'Alan Johnson');
});
});
@@ -73,92 +165,179 @@ 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');
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'
);
});
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!');
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , , false],
'goodbye! Goodbye! GOODBYE! cruel world!'
);
hash = {goodbyes: 'cruel', world: 'world'};
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
hash = { goodbyes: 'cruel', world: 'world' };
shouldCompileTo(
'{{#each .}}{{.}}{{/each}}',
[hash, , , , false],
'cruelworld'
);
});
it('each without context', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
shouldCompileTo(string, [,,,, ], 'cruel !');
shouldCompileTo(string, [, , , ,], 'cruel !');
});
it('each with an object and @key', function() {
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
var string =
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
function Clazz() {
this['<b>#1</b>'] = {text: 'goodbye'};
this[2] = {text: 'GOODBYE'};
this['<b>#1</b>'] = { text: 'goodbye' };
this[2] = { text: 'GOODBYE' };
}
Clazz.prototype.foo = 'fail';
var hash = {goodbyes: new Clazz(), world: 'world'};
var hash = { goodbyes: new Clazz(), world: 'world' };
// Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash);
var expected1 = '&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 = '2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
var expected1 =
'&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 =
'2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
equals(
actual === expected1 || actual === expected2,
true,
'each with object argument iterates over the contents when not empty'
);
shouldCompileTo(string, { goodbyes: {}, world: 'world' }, '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 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');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
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 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');
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'
);
});
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 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!');
equal(
result,
'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 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');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
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 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);
@@ -167,18 +346,34 @@ describe('builtin helpers', function() {
});
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 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');
equal(
result,
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!',
'The @first variable is used'
);
});
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 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);
@@ -187,8 +382,16 @@ describe('builtin helpers', function() {
});
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 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);
@@ -197,8 +400,12 @@ describe('builtin helpers', function() {
});
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 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);
@@ -207,37 +414,78 @@ describe('builtin helpers', function() {
});
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 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');
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 [{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');
var hash = {
goodbyes: function() {
return [
{ 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'
);
});
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'};
var string =
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {
goodbyes: {
a: { text: 'goodbye' },
b: { 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!', 'Empty string key is not skipped');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'Empty string key is not skipped'
);
});
it('data passed to helpers', function() {
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
var hash = {letters: ['a', 'b', 'c']};
var hash = { letters: ['a', 'b', 'c'] };
var template = CompilerContext.compile(string);
var result = template(hash, {
@@ -249,10 +497,16 @@ describe('builtin helpers', function() {
});
it('each on implicit context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
template({});
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
shouldThrow(
function() {
var template = CompilerContext.compile(
'{{#each}}{{text}}! {{/each}}cruel world!'
);
template({});
},
handlebarsEnv.Exception,
'Must pass iterator to #each'
);
});
if (global.Symbol && global.Symbol.iterator) {
@@ -276,13 +530,25 @@ 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 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');
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'
);
});
}
});
@@ -293,9 +559,7 @@ describe('builtin helpers', function() {
return;
}
var $log,
$info,
$error;
var $log, $info, $error;
beforeEach(function() {
$log = console.log;
$info = console.info;
@@ -331,7 +595,7 @@ describe('builtin helpers', function() {
logArg = arg;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals('03', levelArg);
equals('whee', logArg);
});
@@ -367,13 +631,13 @@ describe('builtin helpers', function() {
console.error = $error;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called);
});
it('should handle missing logger', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' },
called = false;
called = false;
console.error = undefined;
console.log = function(log) {
@@ -382,7 +646,7 @@ describe('builtin helpers', function() {
console.log = $log;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called);
});
@@ -396,12 +660,12 @@ describe('builtin helpers', function() {
called = true;
};
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
shouldCompileTo(string, [hash, , , , { level: 'error' }], '');
equals(true, called);
called = false;
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], '');
equals(true, called);
});
it('should handle hash log levels', function() {
@@ -449,11 +713,10 @@ describe('builtin helpers', function() {
/* eslint-enable no-console */
});
describe('#lookup', function() {
it('should lookup arbitrary content', function() {
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -462,7 +725,7 @@ describe('builtin helpers', function() {
});
it('should not fail on undefined value', function() {
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
var template = CompilerContext.compile(string);
var result = template(hash);
+112 -35
View File
@@ -13,37 +13,84 @@ describe('compiler', function() {
equal(compile('foo').equals(compile('foo')), true);
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
equal(
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
compile('{{foo.bar baz "foo" true false bat=1}}')
),
true
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (baz bat=1)}}')
),
true
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
true
);
});
it('should treat as not equal', function() {
equal(compile('foo').equals(compile('bar')), false);
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
equal(
compile('{{foo.bar baz bat=1}}').equals(
compile('{{foo.bar bar bat=1}}')
),
false
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (bar bat=1)}}')
),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(
compile('{{#foo}} {{foo}}{{/foo}}')
),
false
);
});
});
describe('#compile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.compile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
shouldThrow(function() {
Handlebars.compile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
shouldThrow(
function() {
Handlebars.compile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function() {
Handlebars.compile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
});
it('should include the location in the error (row and column)', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
equal(
err.message,
"if doesn't match def - 2:5",
'Checking error message'
);
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
@@ -57,17 +104,28 @@ describe('compiler', function() {
it('should include the location as enumerable property', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(Object.prototype.propertyIsEnumerable.call(err, 'column'), true, 'Checking error column');
equal(
Object.prototype.propertyIsEnumerable.call(err, 'column'),
true,
'Checking error column'
);
}
});
it('can utilize AST instance', function() {
equal(Handlebars.compile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})(), 'Hello');
equal(
Handlebars.compile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})(),
'Hello'
);
});
it('can pass through an empty string', function() {
@@ -75,33 +133,52 @@ describe('compiler', function() {
});
it('should not modify the options.data property(GH-1327)', function() {
var options = {data: [{a: 'foo'}, {a: 'bar'}]};
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
);
});
it('should not modify the options.knownHelpers property(GH-1327)', function() {
var options = {knownHelpers: {}};
var options = { knownHelpers: {} };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ knownHelpers: {} }, 0, 2)
);
});
});
describe('#precompile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.precompile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
shouldThrow(function() {
Handlebars.precompile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
shouldThrow(
function() {
Handlebars.precompile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
);
shouldThrow(
function() {
Handlebars.precompile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
);
});
it('can utilize AST instance', function() {
equal(/return "Hello"/.test(Handlebars.precompile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})), true);
equal(
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})
),
true
);
});
it('can pass through an empty string', function() {
+141 -48
View File
@@ -1,6 +1,6 @@
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 template = CompilerContext.compile('{{hello}}', { data: true });
var helpers = {
hello: function(options) {
@@ -8,7 +8,10 @@ describe('data', function() {
}
};
var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ noun: 'cat' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper');
});
@@ -19,7 +22,9 @@ describe('data', function() {
});
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 template = CompilerContext.compile(
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
);
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
@@ -47,7 +52,11 @@ describe('data', function() {
};
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('hash values can be looked up via @foo', function() {
@@ -59,7 +68,11 @@ describe('data', function() {
};
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('nested parameter data can be looked up via @foo.bar', function() {
@@ -70,8 +83,15 @@ describe('data', function() {
}
};
var result = template({}, { helpers: helpers, data: { world: {bar: 'world' } } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
var result = template(
{},
{ helpers: helpers, data: { world: { bar: 'world' } } }
);
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('nested parameter data does not fail with @world.bar', function() {
@@ -82,8 +102,15 @@ describe('data', function() {
}
};
var result = template({}, { helpers: helpers, data: { foo: {bar: 'world' } } });
equals('Hello undefined', result, '@foo as a parameter retrieves template data');
var result = template(
{},
{ helpers: helpers, data: { foo: { bar: 'world' } } }
);
equals(
'Hello undefined',
result,
'@foo as a parameter retrieves template data'
);
});
it('parameter data throws when using complex scope references', function() {
@@ -96,17 +123,38 @@ describe('data', function() {
it('data can be functions', function() {
var template = CompilerContext.compile('{{@hello}}');
var result = template({}, { data: { hello: function() { return 'hello'; } } });
var result = template(
{},
{
data: {
hello: function() {
return 'hello';
}
}
}
);
equals('hello', result);
});
it('data can be functions with params', function() {
var template = CompilerContext.compile('{{@hello "hello"}}');
var result = template({}, { data: { hello: function(arg) { return arg; } } });
var result = template(
{},
{
data: {
hello: function(arg) {
return arg;
}
}
}
);
equals('hello', result);
});
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 template = CompilerContext.compile(
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
{ data: true }
);
var helpers = {
let: function(options) {
var frame = Handlebars.createFrame(options.data);
@@ -115,19 +163,22 @@ describe('data', function() {
frame[prop] = options.hash[prop];
}
}
return options.fn(this, {data: frame});
return options.fn(this, { data: frame });
}
};
var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
var result = template(
{ bar: { baz: 'hello world' } },
{ helpers: helpers, data: {} }
);
equals('2hello world1', result, 'data variables are inherited downstream');
});
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 template = CompilerContext.compile('{{>myPartial}}', { data: true });
var partials = {
myPartial: CompilerContext.compile('{{hello}}', {data: true})
myPartial: CompilerContext.compile('{{hello}}', { data: true })
};
var helpers = {
@@ -136,12 +187,15 @@ describe('data', function() {
}
};
var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
var result = template(
{ noun: 'cat' },
{ helpers: helpers, partials: partials, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper inside partial');
});
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 template = CompilerContext.compile('{{hello world}}', { data: true });
var helpers = {
hello: function(noun, options) {
@@ -149,12 +203,17 @@ describe('data', function() {
}
};
var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, world: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper');
});
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 template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {
data: true
});
var helpers = {
hello: function(options) {
@@ -165,106 +224,140 @@ describe('data', function() {
}
};
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper');
});
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 template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?'});
return options.fn({ exclaim: '?' });
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world?', result, 'Data output by helper');
});
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 template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
},
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'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
);
equals('#win happy world?', result, 'Data output by helper');
});
it('you can override inherited data when invoking a helper', function() {
var template = CompilerContext.compile('{{#hello}}{{world zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
return options.fn(
{ exclaim: '?', zomg: 'world' },
{ data: { adjective: 'sad' } }
);
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'planet' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper');
});
it('you can override inherited data when invoking a helper with depth', function() {
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper');
});
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: {} });
var result = template({ foo: 'hello' }, { data: {} });
equals('hello', result);
result = template({foo: 'hello'}, {});
result = template({ foo: 'hello' }, {});
equals('hello', result);
});
it('passed root values take priority', function() {
var template = CompilerContext.compile('{{@root.foo}}');
var result = template({}, { data: {root: {foo: 'hello'} } });
var result = template({}, { data: { root: { foo: 'hello' } } });
equals('hello', result);
});
});
describe('nesting', function() {
it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile('{{#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});
var template = CompilerContext.compile(
'{{#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 });
}
},
data: {
depth: 0
}
},
data: {
depth: 0
}
});
);
equals('2 1 0', result);
});
});
+5 -3
View File
@@ -1,12 +1,11 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
var distHandlebars = fs.readFileSync(
require.resolve('../../' + filename),
'utf-8'
);
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
+48 -18
View File
@@ -1,6 +1,6 @@
var global = (function() {
return this;
}());
})();
var AssertError;
if (Error.captureStackTrace) {
@@ -28,10 +28,19 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
/**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
string,
hashOrArray,
partials,
expected,
message
) {
var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) {
throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
throw new AssertError(
"'" + result + "' should === '" + expected + "': " + message,
shouldCompileToWithPartials
);
}
};
@@ -39,17 +48,18 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.compileWithPartials = function(string, hashOrArray, partials) {
var template,
ary,
options;
var template, ary, options;
if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash;
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
ary = [];
ary.push(hashOrArray[0]); // input
ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]});
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options =
typeof hashOrArray[3] === 'object'
? hashOrArray[3]
: { compat: hashOrArray[3] };
if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4];
@@ -58,18 +68,23 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
ary = [hashOrArray];
}
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
string,
options
);
return template.apply(this, ary);
};
/**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/
*/
global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) {
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
throw new AssertError(
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
equals
);
}
};
@@ -86,8 +101,19 @@ global.shouldThrow = function(callback, type, msg) {
if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught);
}
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
if (
msg &&
!(msg.test ? msg.test(caught.message) : msg === caught.message)
) {
throw new AssertError(
'Throw mismatch: Expected ' +
caught.message +
' to match ' +
msg +
'\n\n' +
caught.stack,
shouldThrow
);
}
}
if (failed) {
@@ -95,18 +121,17 @@ global.shouldThrow = function(callback, type, msg) {
}
};
global.expectTemplate = function(templateAsString) {
return new HandlebarsTestBench(templateAsString);
};
function HandlebarsTestBench(templateAsString) {
this.templateAsString = templateAsString;
this.helpers = {};
this.partials = {};
this.input = {};
this.message = 'Template' + templateAsString + ' does not evaluate to expected output';
this.message =
'Template' + templateAsString + ' does not evaluate to expected output';
this.compileOptions = {};
this.runtimeOptions = {};
}
@@ -146,7 +171,11 @@ HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
};
// 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,
msg
) {
var self = this;
expect(function() {
self._compileAndExeute();
@@ -154,7 +183,8 @@ HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg)
};
HandlebarsTestBench.prototype._compileAndExeute = function() {
var compile = Object.keys(this.partials).length > 0
var compile =
Object.keys(this.partials).length > 0
? CompilerContext.compileWithPartial
: CompilerContext.compile;
-1
View File
@@ -3,7 +3,6 @@ require('./common');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
+12 -8
View File
@@ -1,11 +1,11 @@
/* eslint-disable no-console */
var fs = require('fs'),
Mocha = require('mocha'),
path = require('path');
Mocha = require('mocha'),
path = require('path');
var errors = 0,
testDir = path.dirname(__dirname),
grep = process.argv[2];
testDir = path.dirname(__dirname),
grep = process.argv[2];
// Lazy hack, but whatever
if (grep === '--min') {
@@ -13,9 +13,14 @@ if (grep === '--min') {
grep = undefined;
}
var files = fs.readdirSync(testDir)
.filter(function(name) { return (/.*\.js$/).test(name); })
.map(function(name) { return testDir + path.sep + name; });
var files = fs
.readdirSync(testDir)
.filter(function(name) {
return /.*\.js$/.test(name);
})
.map(function(name) {
return testDir + path.sep + name;
});
if (global.minimizedTest) {
run('./runtime', function() {
@@ -37,7 +42,6 @@ if (global.minimizedTest) {
});
}
function run(env, callback) {
var mocha = new Mocha();
mocha.ui('bdd');
+10 -4
View File
@@ -1,12 +1,11 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js';
}
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
vm.runInThisContext(
fs.readFileSync(__dirname + '/../../' + filename),
filename
);
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
@@ -30,7 +32,11 @@ global.CompilerContext = {
compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
return compiler.precompile(
precompileTemplate,
precompileOptions,
handlebarsEnv
);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
+735 -239
View File
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -13,7 +13,10 @@ describe('javascript-compiler api', function() {
});
it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(
parent,
name
) {
return parent + '.bar_' + name;
};
/* eslint-disable camelcase */
@@ -43,7 +46,7 @@ describe('javascript-compiler api', function() {
};
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') {
throw new Error('It didn\'t work');
throw new Error("It didn't work");
}
};
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
@@ -54,7 +57,8 @@ describe('javascript-compiler api', function() {
beforeEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
$superCreate =
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
@@ -69,7 +73,9 @@ describe('javascript-compiler api', function() {
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
string
) {
return $superAppend.call(this, [string, ' + "_foo"']);
};
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
+257 -95
View File
@@ -68,7 +68,10 @@ describe('parser', function() {
equals(astFor('{{null}}'), '{{ NULL [] }}\n');
});
it('parses mustaches with undefined and null parameters', function() {
equals(astFor('{{foo undefined null}}'), '{{ PATH:foo [UNDEFINED, NULL] }}\n');
equals(
astFor('{{foo undefined null}}'),
'{{ PATH:foo [UNDEFINED, NULL] }}\n'
);
});
it('parses mustaches with DATA parameters', function() {
@@ -78,23 +81,53 @@ describe('parser', function() {
it('parses mustaches with hash arguments', function() {
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
equals(astFor('{{foo bar=true}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n');
equals(astFor('{{foo bar=false}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n');
equals(astFor('{{foo bar=@baz}}'), '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n');
equals(
astFor('{{foo bar=true}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo bar=false}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
);
equals(
astFor('{{foo bar=@baz}}'),
'{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
);
equals(astFor('{{foo bar=baz bat=bam}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n');
equals(astFor('{{foo bar=baz bat="bam"}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n');
equals(
astFor('{{foo bar=baz bat=bam}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
);
equals(
astFor('{{foo bar=baz bat="bam"}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam"}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=1}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=true}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=false}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
equals(
astFor('{{foo omg bar=baz bat="bam"}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
);
});
it('parses contents followed by a mustache', function() {
equals(astFor('foo bar {{baz}}'), 'CONTENT[ \'foo bar \' ]\n{{ PATH:baz [] }}\n');
equals(
astFor('foo bar {{baz}}'),
"CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
);
});
it('parses a partial', function() {
@@ -108,47 +141,81 @@ describe('parser', function() {
});
it('parses a partial with hash', function() {
equals(astFor('{{> foo bar=bat}}'), '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n');
equals(
astFor('{{> foo bar=bat}}'),
'{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
);
});
it('parses a partial with context and hash', function() {
equals(astFor('{{> foo bar bat=baz}}'), '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n');
equals(
astFor('{{> foo bar bat=baz}}'),
'{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
);
});
it('parses a partial with a complex name', function() {
equals(astFor('{{> shared/partial?.bar}}'), '{{> PARTIAL:shared/partial?.bar }}\n');
equals(
astFor('{{> shared/partial?.bar}}'),
'{{> PARTIAL:shared/partial?.bar }}\n'
);
});
it('parsers partial blocks', function() {
equals(astFor('{{#> foo}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
equals(
astFor('{{#> foo}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('should handle parser block mismatch', function() {
shouldThrow(function() {
astFor('{{#> goodbyes}}{{/hellos}}');
}, Error, (/goodbyes doesn't match hellos/));
shouldThrow(
function() {
astFor('{{#> goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('parsers partial blocks with arguments', function() {
equals(astFor('{{#> foo context hash=value}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
equals(
astFor('{{#> foo context hash=value}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('parses a comment', function() {
equals(astFor('{{! this is a comment }}'), "{{! ' this is a comment ' }}\n");
equals(
astFor('{{! this is a comment }}'),
"{{! ' this is a comment ' }}\n"
);
});
it('parses a multi-line comment', function() {
equals(astFor('{{!\nthis is a multi-line comment\n}}'), "{{! '\nthis is a multi-line comment\n' }}\n");
equals(
astFor('{{!\nthis is a multi-line comment\n}}'),
"{{! '\nthis is a multi-line comment\n' }}\n"
);
});
it('parses an inverse section', function() {
equals(astFor('{{#foo}} bar {{^}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses an inverse (else-style) section', function() {
equals(astFor('{{#foo}} bar {{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses multiple inverse sections', function() {
equals(astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses empty blocks', function() {
@@ -156,31 +223,52 @@ describe('parser', function() {
});
it('parses empty blocks with empty inverse section', function() {
equals(astFor('{{#foo}}{{^}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
equals(
astFor('{{#foo}}{{^}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses empty blocks with empty inverse (else-style) section', function() {
equals(astFor('{{#foo}}{{else}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
equals(
astFor('{{#foo}}{{else}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses non-empty blocks with empty inverse section', function() {
equals(astFor('{{#foo}} bar {{^}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
equals(
astFor('{{#foo}} bar {{^}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals(astFor('{{#foo}} bar {{else}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
equals(
astFor('{{#foo}} bar {{else}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses empty blocks with non-empty inverse section', function() {
equals(astFor('{{#foo}}{{^}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
equals(
astFor('{{#foo}}{{^}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals(astFor('{{#foo}}{{else}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
equals(
astFor('{{#foo}}{{else}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses a standalone inverse section', function() {
equals(astFor('{{^foo}}bar{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n");
equals(
astFor('{{^foo}}bar{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
);
});
it('throws on old inverse section', function() {
shouldThrow(function() {
@@ -189,105 +277,179 @@ describe('parser', function() {
});
it('parses block with block params', function() {
equals(astFor('{{#foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{#foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses inverse block with block params', function() {
equals(astFor('{{^foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{^foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses chained inverse block with block params', function() {
equals(astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('raises if there\'s a Parse error', function() {
shouldThrow(function() {
astFor('foo{{^}}bar');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{foo}');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{foo &}}');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{#goodbyes}}{{/hellos}}');
}, Error, /goodbyes doesn't match hellos/);
it("raises if there's a Parse error", function() {
shouldThrow(
function() {
astFor('foo{{^}}bar');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo &}}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{#goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
shouldThrow(function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
}, Error, /goodbyes doesn't match hellos/);
shouldThrow(
function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('should handle invalid paths', function() {
shouldThrow(function() {
astFor('{{foo/../bar}}');
}, Error, /Invalid path: foo\/\.\. - 1:2/);
shouldThrow(function() {
astFor('{{foo/./bar}}');
}, Error, /Invalid path: foo\/\. - 1:2/);
shouldThrow(function() {
astFor('{{foo/this/bar}}');
}, Error, /Invalid path: foo\/this - 1:2/);
shouldThrow(
function() {
astFor('{{foo/../bar}}');
},
Error,
/Invalid path: foo\/\.\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/./bar}}');
},
Error,
/Invalid path: foo\/\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/this/bar}}');
},
Error,
/Invalid path: foo\/this - 1:2/
);
});
it('knows how to report the correct line number in errors', function() {
shouldThrow(function() {
astFor('hello\nmy\n{{foo}');
}, Error, /Parse error on line 3/);
shouldThrow(function() {
astFor('hello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 5/);
shouldThrow(
function() {
astFor('hello\nmy\n{{foo}');
},
Error,
/Parse error on line 3/
);
shouldThrow(
function() {
astFor('hello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 5/
);
});
it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(function() {
astFor('\n\nhello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 7/);
shouldThrow(
function() {
astFor('\n\nhello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 7/
);
});
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
equals(astFor({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
}), 'CONTENT[ \'Hello\' ]\n');
equals(
astFor({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
}),
"CONTENT[ 'Hello' ]\n"
);
});
});
describe('directives', function() {
it('should parse block directives', function() {
equals(astFor('{{#* foo}}{{/foo}}'), 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
equals(
astFor('{{#* foo}}{{/foo}}'),
'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
);
});
it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
});
it('should fail if directives have inverse', function() {
shouldThrow(function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
}, Error, /Unexpected inverse/);
shouldThrow(
function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
},
Error,
/Unexpected inverse/
);
});
});
it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse('\n'
+ ' {{#if foo}}\n'
+ ' {{bar}}\n'
+ ' {{else}} {{baz}}\n'
+ '\n'
+ ' {{/if}}\n'
+ ' ');
var p = Handlebars.parse(
'\n' +
' {{#if foo}}\n' +
' {{bar}}\n' +
' {{else}} {{baz}}\n' +
'\n' +
' {{/if}}\n' +
' '
);
// We really need a deep equals but for now this should be stable...
equals(JSON.stringify(p.loc), JSON.stringify({
start: { line: 1, column: 0 },
end: { line: 7, column: 4 }
}));
equals(JSON.stringify(p.body[1].program.loc), JSON.stringify({
start: { line: 2, column: 13 },
end: { line: 4, column: 7 }
}));
equals(JSON.stringify(p.body[1].inverse.loc), JSON.stringify({
start: { line: 4, column: 15 },
end: { line: 6, column: 5 }
}));
equals(
JSON.stringify(p.loc),
JSON.stringify({
start: { line: 1, column: 0 },
end: { line: 7, column: 4 }
})
);
equals(
JSON.stringify(p.body[1].program.loc),
JSON.stringify({
start: { line: 2, column: 13 },
end: { line: 4, column: 7 }
})
);
equals(
JSON.stringify(p.body[1].inverse.loc),
JSON.stringify({
start: { line: 4, column: 15 },
end: { line: 6, column: 5 }
})
);
});
});
+504 -162
View File
@@ -2,115 +2,224 @@ describe('partials', function() {
it('basic partials', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
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) ');
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
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) '
);
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
});
it('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 hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
partial: 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) ');
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) '
);
});
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 hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
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');
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'
);
});
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');
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'
);
});
it('partials with no context', function() {
var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}],
true,
'Dudes: () () ');
'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 () ');
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, { dude: partial }, { explicitPartialContext: true }],
true,
'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');
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'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');
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Empty'
);
});
it('partials with duplicate parameters', function() {
shouldThrow(function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
}, Error, 'Unsupported number of partial arguments: 2 - 1:7');
shouldThrow(
function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
},
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.');
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.'
);
});
it('partial in a partial', function() {
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
var dude = '{{name}} {{> url}} ';
var url = '<a href="{{url}}">{{url}}</a>';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, 'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ', 'Partials are rendered inside of other partials');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ',
'Partials are rendered inside of other partials'
);
});
it('rendering undefined partial throws an exception', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('registering undefined partial throws an exception', function() {
shouldThrow(function() {
var undef;
handlebarsEnv.registerPartial('undefined_test', undef);
}, Handlebars.Exception, 'Attempting to register a partial called "undefined_test" as undefined');
shouldThrow(
function() {
var undef;
handlebarsEnv.registerPartial('undefined_test', undef);
},
Handlebars.Exception,
'Attempting to register a partial called "undefined_test" as undefined'
);
});
it('rendering template partial in vm mode throws an exception', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('rendering function partial in vm mode', function() {
@@ -118,30 +227,57 @@ describe('partials', function() {
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.');
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.'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes: Jeepers Creepers',
'Regular selectors can follow a partial'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude.thing': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal with points in paths'
);
});
it('Global Partials', function() {
@@ -149,8 +285,14 @@ describe('partials', function() {
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
handlebarsEnv.unregisterPartial('globalTest');
equals(handlebarsEnv.partials.globalTest, undefined);
@@ -163,51 +305,95 @@ describe('partials', function() {
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 404: dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with string', function() {
var string = 'Dudes: {{> \'+404/asdf?.bar\'}}';
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');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
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: ');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: '
);
});
it('throw on missing partial', function() {
var compile = handlebarsEnv.compile;
handlebarsEnv.compile = undefined;
shouldThrow(function() {
shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], '');
}, Error, /The partial dude could not be compiled/);
shouldThrow(
function() {
shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], '');
},
Error,
/The partial dude could not be compiled/
);
handlebarsEnv.compile = compile;
});
@@ -217,224 +403,309 @@ describe('partials', function() {
'{{#> dude}}success{{/dude}}',
[{}, {}, {}],
true,
'success');
'success'
);
});
it('should execute default block with proper context', function() {
shouldCompileToWithPartials(
'{{#> dude context}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {}],
[{ context: { value: 'success' } }, {}, {}],
true,
'success');
'success'
);
});
it('should propagate block parameters to default block', function() {
shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {}],
[{ context: { value: 'success' } }, {}, {}],
true,
'success');
'success'
);
});
it('should not use partial block if partial exists', function() {
shouldCompileToWithPartials(
'{{#> dude}}fail{{/dude}}',
[{}, {}, {dude: 'success'}],
[{}, {}, { dude: 'success' }],
true,
'success');
'success'
);
});
it('should render block from partial', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }}'}],
[{}, {}, { dude: '{{> @partial-block }}' }],
true,
'success');
'success'
);
});
it('should be able to render the partial-block twice', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}],
true,
'success success');
'{{#> dude}}success{{/dude}}',
[{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }],
true,
'success success'
);
});
it('should render block from partial with context', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success');
'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: '<code>before-block: {{@root/value}} {{> @partial-block }}</code>'}],
[
{ value: 'success' },
{},
{
dude:
'<code>before-block: {{@root/value}} {{> @partial-block }}</code>'
}
],
true,
'<code>before-block: success in-block: success</code>');
'<code>before-block: success in-block: success</code>'
);
});
it('should allow the #each-helper to be used along with partial-blocks', function() {
shouldCompileToWithPartials(
'<template>{{#> list value}}value = {{.}}{{/list}}</template>',
[
{value: ['a', 'b', 'c']},
{ value: ['a', 'b', 'c'] },
{},
{
list: '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
list:
'<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
}
],
true,
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>');
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>'
);
});
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');
'{{#> dude}}{{value}}{{/dude}}',
[
{ context: { value: 'success' } },
{},
{
dude:
'{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
}
],
true,
'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}}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success');
'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 }}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{> @partial-block }}' }
],
true,
'success');
'success'
);
});
it('should render nested partial blocks', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>');
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>'
);
});
it('should render nested partial blocks at different nesting levels', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>');
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>'
);
});
it('should render nested partial blocks at different nesting levels (twice)', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>');
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>'
);
});
it('should render nested partial blocks (twice at each level)', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
}
],
true,
'<template><outer>' +
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>');
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>'
);
});
});
describe('inline partials', function() {
it('should define inline partials for template', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
});
it('should overwrite multiple partials in the same template', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'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/);
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
shouldThrow(
function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
{},
'success'
);
},
Error,
/myPartial could not/
);
});
it('should override global partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {hash: {}, partials: {myPartial: function() { return 'fail'; }}}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{
hash: {},
partials: {
myPartial: function() {
return 'fail';
}
}
},
'success'
);
});
it('should override template partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
});
it('should override partials down the entire stack', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', {}, 'success');
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}',
{},
'success'
);
});
it('should define inline partials for partial call', function() {
shouldCompileToWithPartials(
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success');
'success'
);
});
it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success');
'success'
);
});
it('should render nested inline partials', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success</outer-block></inner>');
'<inner><outer-block>success</outer-block></inner>'
);
});
it('should render nested inline partials with partial-blocks on different nesting levels', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success</outer-block></inner>success');
'<inner><outer-block>success</outer-block></inner>success'
);
});
it('should render nested inline partials (twice at each level)', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>');
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>'
);
});
});
@@ -442,8 +713,8 @@ describe('partials', function() {
if (Handlebars.compile) {
var env = Handlebars.create();
env.registerPartial('partial', '{{foo}}');
var template = env.compile('{{foo}} {{> partial}}', {noEscape: true});
equal(template({foo: '<'}), '< <');
var template = env.compile('{{foo}} {{> partial}}', { noEscape: true });
equal(template({ foo: '<' }), '< <');
}
});
@@ -451,25 +722,52 @@ describe('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');
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'
);
});
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');
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'
);
});
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');
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'
);
});
});
@@ -477,26 +775,70 @@ describe('partials', 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 ');
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 '
);
});
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 ');
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 '
);
});
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 ');
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 '
);
});
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 ');
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 '
);
});
});
});
+185 -92
View File
@@ -6,27 +6,24 @@ describe('precompiler', function() {
}
var Handlebars = require('../lib'),
Precompiler = require('../dist/cjs/precompiler'),
fs = require('fs'),
uglify = require('uglify-js');
Precompiler = require('../dist/cjs/precompiler'),
fs = require('fs'),
uglify = require('uglify-js');
var log,
logFunction,
errorLog,
errorLogFunction,
precompile,
minify,
emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars',
name: 'empty',
source: ''
},
file,
content,
writeFileSync;
logFunction,
errorLog,
errorLogFunction,
precompile,
minify,
emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars',
name: 'empty',
source: ''
},
file,
content,
writeFileSync;
/**
* Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded.
@@ -87,102 +84,166 @@ describe('precompiler', function() {
});
it('should output version', function() {
Precompiler.cli({templates: [], version: true});
Precompiler.cli({ templates: [], version: true });
equals(log, Handlebars.VERSION);
});
it('should throw if lacking templates', function() {
shouldThrow(function() {
Precompiler.cli({templates: []});
}, Handlebars.Exception, 'Must define at least one template or directory.');
shouldThrow(
function() {
Precompiler.cli({ templates: [] });
},
Handlebars.Exception,
'Must define at least one template or directory.'
);
});
it('should handle empty/filtered directories', function() {
Precompiler.cli({hasDirectory: true, templates: []});
Precompiler.cli({ hasDirectory: true, templates: [] });
// Success is not throwing
});
it('should throw when combining simple and minimized', function() {
shouldThrow(function() {
Precompiler.cli({templates: [__dirname], simple: true, min: true});
}, Handlebars.Exception, 'Unable to minimize simple output');
shouldThrow(
function() {
Precompiler.cli({ templates: [__dirname], simple: true, min: true });
},
Handlebars.Exception,
'Unable to minimize simple output'
);
});
it('should throw when combining simple and multiple templates', function() {
shouldThrow(function() {
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
shouldThrow(
function() {
Precompiler.cli({
templates: [
__dirname + '/artifacts/empty.handlebars',
__dirname + '/artifacts/empty.handlebars'
],
simple: true
});
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should throw when missing name', function() {
shouldThrow(function() {
Precompiler.cli({templates: [{source: ''}], amd: true});
}, Handlebars.Exception, 'Name missing for template');
shouldThrow(
function() {
Precompiler.cli({ templates: [{ source: '' }], amd: true });
},
Handlebars.Exception,
'Name missing for template'
);
});
it('should throw when combining simple and directories', function() {
shouldThrow(function() {
Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
shouldThrow(
function() {
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should output simple templates', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true });
equal(log, 'simple\n');
});
it('should default to simple templates', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [{source: ''}]});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [{ source: '' }] });
equal(log, 'simple\n');
});
it('should output amd templates', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], amd: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true });
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
namespace: 'foo'
});
equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output amd partials', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd partials', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
partial: true
});
equal(/return Handlebars\.partials\[/.test(log), false);
equal(/template\(amd\)/.test(log), true);
});
it('should output commonjs templates', function() {
Handlebars.precompile = function() { return 'commonjs'; };
Precompiler.cli({templates: [emptyTemplate], commonjs: true});
Handlebars.precompile = function() {
return 'commonjs';
};
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
equal(/template\(commonjs\)/.test(log), true);
});
it('should set data flag', function() {
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true});
Handlebars.precompile = function(data, options) {
equal(options.data, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, data: true });
equal(log, 'simple\n');
});
it('should set known helpers', function() {
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'});
Handlebars.precompile = function(data, options) {
equal(options.knownHelpers.foo, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
equal(log, 'simple\n');
});
it('should output to file system', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({
templates: [emptyTemplate],
simple: true,
output: 'file!'
});
equal(file, 'file!');
equal(content, 'simple\n');
equal(log, '');
});
it('should output minimized templates', function() {
Handlebars.precompile = function() { return 'amd'; };
uglify.minify = function() { return {code: 'min'}; };
Precompiler.cli({templates: [emptyTemplate], min: true});
Handlebars.precompile = function() {
return 'amd';
};
uglify.minify = function() {
return { code: 'min' };
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(log, 'min');
});
@@ -191,8 +252,10 @@ describe('precompiler', function() {
error.code = 'MODULE_NOT_FOUND';
mockRequireUglify(error, function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], min: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(/template\(amd\)/.test(log), true);
equal(/\n/.test(log), true);
equal(/Code minimization is disabled/.test(errorLog), true);
@@ -201,23 +264,33 @@ describe('precompiler', function() {
it('should fail on errors (other than missing module) while loading uglify-js', function() {
mockRequireUglify(new Error('Mock Error'), function() {
shouldThrow(function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], min: true});
}, Error, 'Mock Error');
shouldThrow(
function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
},
Error,
'Mock Error'
);
});
});
it('should output map', function() {
Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'});
Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
});
it('should output map', function() {
Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'});
Precompiler.cli({
templates: [emptyTemplate],
min: true,
map: 'foo.js.map'
});
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
@@ -225,35 +298,48 @@ describe('precompiler', function() {
describe('#loadTemplates', function() {
it('should throw on missing template', function(done) {
Precompiler.loadTemplates({files: ['foo']}, function(err) {
Precompiler.loadTemplates({ files: ['foo'] }, function(err) {
equal(err.message, 'Unable to open template file "foo"');
done();
});
});
it('should enumerate directories by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hbs'}, function(err, opts) {
equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2');
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
function(err, opts) {
equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2');
done(err);
}
);
});
it('should enumerate all templates by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'handlebars'}, function(err, opts) {
equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1');
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
function(err, opts) {
equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1');
done(err);
}
);
});
it('should handle regular expression characters in extensions', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hb(s'}, function(err) {
// Success is not throwing
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
function(err) {
// Success is not throwing
done(err);
}
);
});
it('should handle BOM', function(done) {
var opts = {files: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true};
var opts = {
files: [__dirname + '/artifacts/bom.handlebars'],
extension: 'handlebars',
bom: true
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].source, 'a');
done(err);
@@ -261,7 +347,11 @@ describe('precompiler', function() {
});
it('should handle different root', function(done) {
var opts = {files: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'};
var opts = {
files: [__dirname + '/artifacts/empty.handlebars'],
simple: true,
root: 'foo/'
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
done(err);
@@ -269,7 +359,7 @@ describe('precompiler', function() {
});
it('should accept string inputs', function(done) {
var opts = {string: ''};
var opts = { string: '' };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, undefined);
equal(opts.templates[0].source, '');
@@ -277,7 +367,7 @@ describe('precompiler', function() {
});
});
it('should accept string array inputs', function(done) {
var opts = {string: ['', 'bar'], name: ['beep', 'boop']};
var opts = { string: ['', 'bar'], name: ['beep', 'boop'] };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, 'beep');
equal(opts.templates[0].source, '');
@@ -288,7 +378,7 @@ describe('precompiler', function() {
});
it('should accept stdin input', function(done) {
var stdin = require('mock-stdin').stdin();
Precompiler.loadTemplates({string: '-'}, function(err, opts) {
Precompiler.loadTemplates({ string: '-' }, function(err, opts) {
equal(opts.templates[0].source, 'foo');
done(err);
});
@@ -297,9 +387,12 @@ describe('precompiler', function() {
stdin.end();
});
it('error on name missing', function(done) {
var opts = {string: ['', 'bar']};
var opts = { string: ['', 'bar'] };
Precompiler.loadTemplates(opts, function(err) {
equal(err.message, 'Number of names did not match the number of string inputs');
equal(
err.message,
'Number of names did not match the number of string inputs'
);
done();
});
});
+222 -107
View File
@@ -1,93 +1,134 @@
describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() {
var data = {
'books': [{
'title': 'The origin of species',
'author': {
'name': 'Charles Darwin'
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');
shouldCompileTo(
string,
data,
'The origin of speciesCharles DarwinLazarillo de Tormes',
'Renders without an undefined property error'
);
});
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");
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"
);
});
it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}';
var data = { 'arr': [1, 2] };
var data = { arr: [1, 2] };
shouldCompileTo(string, data, '1, 2', 'it works as expected');
});
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
+ '{{! Just ignore this business. }}\n'
+ 'Check this out:\n'
+ '{{#hasThings}}\n'
+ '<ul>\n'
+ '{{#things}}\n'
+ '<li class={{className}}>{{word}}</li>\n'
+ '{{/things}}</ul>.\n'
+ '{{/hasThings}}\n'
+ '{{^hasThings}}\n'
+ '\n'
+ '<small>Nothing to check out...</small>\n'
+ '{{/hasThings}}';
var string =
'<strong>This is a slightly more complicated {{thing}}.</strong>.\n' +
'{{! Just ignore this business. }}\n' +
'Check this out:\n' +
'{{#hasThings}}\n' +
'<ul>\n' +
'{{#things}}\n' +
'<li class={{className}}>{{word}}</li>\n' +
'{{/things}}</ul>.\n' +
'{{/hasThings}}\n' +
'{{^hasThings}}\n' +
'\n' +
'<small>Nothing to check out...</small>\n' +
'{{/hasThings}}';
var data = {
thing: function() {
return 'blah';
},
things: [
{className: 'one', word: '@fat'},
{className: 'two', word: '@dhg'},
{className: 'three', word: '@sayrer'}
{ className: 'one', word: '@fat' },
{ className: 'two', word: '@dhg' },
{ className: 'three', word: '@sayrer' }
],
hasThings: function() {
return true;
}
};
var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
+ 'Check this out:\n'
+ '<ul>\n'
+ '<li class=one>@fat</li>\n'
+ '<li class=two>@dhg</li>\n'
+ '<li class=three>@sayrer</li>\n'
+ '</ul>.\n';
var output =
'<strong>This is a slightly more complicated blah.</strong>.\n' +
'Check this out:\n' +
'<ul>\n' +
'<li class=one>@fat</li>\n' +
'<li class=two>@dhg</li>\n' +
'<li class=three>@sayrer</li>\n' +
'</ul>.\n';
shouldCompileTo(string, data, output);
});
it('GH-408: Multiple loops fail', function() {
var context = [
{ name: 'John Doe', location: { city: 'Chicago' } },
{ name: 'Jane Doe', location: { city: 'New York'} }
{ name: 'Jane Doe', location: { city: 'New York' } }
];
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
var template = CompilerContext.compile(
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
);
var result = template(context);
equals(result, 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', 'It should output multiple times');
equals(
result,
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
'It should output multiple times'
);
});
it('GS-428: Nested if else rendering', function() {
var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var succeedingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = {
blk: function(block) { return block.fn(''); },
inverse: function(block) { return block.inverse(''); }
blk: function(block) {
return block.fn('');
},
inverse: function(block) {
return block.inverse('');
}
};
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
@@ -95,7 +136,7 @@ describe('Regressions', function() {
});
it('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
});
it('GH-375: Unicode line terminators', function() {
@@ -104,11 +145,11 @@ describe('Regressions', function() {
it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */
Object.prototype[0xD834] = true;
Object.prototype[0xd834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xD834];
delete Object.prototype[0xd834];
/* eslint-enable no-extend-native */
});
@@ -123,34 +164,46 @@ describe('Regressions', function() {
it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}';
var data = { 'arr': [1, 2] };
var data = { arr: [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
});
it('Mustache man page', function() {
var string = 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
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
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');
shouldCompileTo(
string,
data,
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
'the hello world mustache example works'
);
});
it('GH-731: zero context rendering', function() {
shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is ~ ');
shouldCompileTo(
'{{#foo}} This is {{bar}} ~ {{/foo}}',
{ foo: 0, bar: 'OK' },
' This is ~ '
);
});
it('GH-820: zero pathed rendering', function() {
shouldCompileTo('{{foo.bar}}', {foo: 0}, '');
shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
});
it('GH-837: undefined values for helpers', function() {
var helpers = {
str: function(value) { return value + ''; }
str: function(value) {
return value + '';
}
};
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
@@ -159,15 +212,13 @@ describe('Regressions', function() {
it('GH-926: Depths and de-dupe', function() {
var context = {
name: 'foo',
data: [
1
],
notData: [
1
]
data: [1],
notData: [1]
};
var template = CompilerContext.compile('{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}');
var template = CompilerContext.compile(
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
);
var result = template(context);
equals(result, 'foo');
@@ -176,11 +227,15 @@ describe('Regressions', function() {
it('GH-1021: Each empty string key', function() {
var data = {
'': 'foo',
'name': 'Chris',
'value': 10000
name: 'Chris',
value: 10000
};
shouldCompileTo('{{#each data}}Key: {{@key}}\n{{/each}}', {data: data}, 'Key: \nKey: name\nKey: value\n');
shouldCompileTo(
'{{#each data}}Key: {{@key}}\n{{/each}}',
{ data: data },
'Key: \nKey: name\nKey: value\n'
);
});
it('GH-1054: Should handle simple safe string responses', function() {
@@ -194,18 +249,27 @@ describe('Regressions', function() {
}
};
shouldCompileToWithPartials(root, [{}, helpers, partials], true, '<partial>');
shouldCompileToWithPartials(
root,
[{}, helpers, partials],
true,
'<partial>'
);
});
it('GH-1065: Sparse arrays', function() {
var array = [];
array[1] = 'foo';
array[3] = 'bar';
shouldCompileTo('{{#each array}}{{@index}}{{.}}{{/each}}', {array: array}, '1foo3bar');
shouldCompileTo(
'{{#each array}}{{@index}}{{.}}{{/each}}',
{ array: array },
'1foo3bar'
);
});
it('GH-1093: Undefined helper context', function() {
var obj = {foo: undefined, bar: 'bat'};
var obj = { foo: undefined, bar: 'bat' };
var helpers = {
helper: function() {
// It's valid to execute a block against an undefined context, but
@@ -216,28 +280,45 @@ describe('Regressions', function() {
}
}
// And to make IE happy, check for the known string as length is not enumerated.
return (this === 'bat' ? 'found' : 'not');
return this === 'bat' ? 'found' : 'not';
}
};
shouldCompileTo('{{#each obj}}{{{helper}}}{{.}}{{/each}}', [{obj: obj}, helpers], 'notfoundbat');
shouldCompileTo(
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
[{ obj: obj }, helpers],
'notfoundbat'
);
});
it('should support multiple levels of inline partials', function() {
var string = '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var string =
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout: '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'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}}'
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}';
@@ -249,51 +330,63 @@ describe('Regressions', function() {
});
it('GH-1135 : Context handling within each iteration', function() {
var obj = {array: [1], name: 'John'};
var obj = { array: [1], name: 'John' };
var helpers = {
myif: function(conditional, options) {
if (conditional) {
return options.fn(this);
return options.fn(this);
} else {
return options.inverse(this);
return options.inverse(this);
}
}
};
shouldCompileTo(
'{{#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 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'
);
});
it('GH-1186: Support block params for existing programs', function() {
var string =
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}'
+ '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}'
+ '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
'{{#*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', '');
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, '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 ', '');
var string =
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
shouldCompileTo(
string,
{ value: 'parent', list: [null, 'a'] },
'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}}',
partialWithBlock:
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'template block partial block template');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'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() {
it('should compile and execute templates', function() {
var newHandlebarsInstance = Handlebars.create();
@@ -301,31 +394,53 @@ describe('Regressions', function() {
newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase();
});
var result = newHandlebarsInstance.templates['test.hbs']({name: 'yehuda'});
var result = newHandlebarsInstance.templates['test.hbs']({
name: 'yehuda'
});
equals(result.trim(), 'YEHUDA');
});
it('should call "helperMissing" if a helper is missing', function() {
var newHandlebarsInstance = Handlebars.create();
shouldThrow(function() {
registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.templates['test.hbs']({});
}, Handlebars.Exception, 'Missing helper: "loud"');
shouldThrow(
function() {
registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.templates['test.hbs']({});
},
Handlebars.Exception,
'Missing helper: "loud"'
);
});
// This is a only slightly modified precompiled templated from compiled with 4.2.1
function registerTemplate(Handlebars) {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['test.hbs'] = template({'compiler': [7, '>= 4.0.0'], 'main': function(container, depth0, helpers, partials, data) {
return container.escapeExpression((helpers.loud || (depth0 && depth0.loud) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.name : depth0), {'name': 'loud', 'hash': {}, 'data': data}))
+ '\n\n';
}, 'useData': true});
var template = Handlebars.template,
templates = (Handlebars.templates = Handlebars.templates || {});
templates['test.hbs'] = template({
compiler: [7, '>= 4.0.0'],
main: function(container, depth0, helpers, partials, data) {
return (
container.escapeExpression(
(
helpers.loud ||
(depth0 && depth0.loud) ||
helpers.helperMissing
).call(
depth0 != null ? depth0 : container.nullContext || {},
depth0 != null ? depth0.name : depth0,
{ name: 'loud', hash: {}, data: data }
)
) + '\n\n'
);
},
useData: true
});
}
});
it('should allow hash with protected array names', function() {
var obj = {array: [1], name: 'John'};
var obj = { array: [1], name: 'John' };
var helpers = {
helpa: function(options) {
return options.hash.length;
@@ -352,7 +467,7 @@ describe('Regressions', function() {
it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({
'dude': 'I am a partial'
dude: 'I am a partial'
});
var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
+2 -2
View File
@@ -5,7 +5,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
equal(template, require('./artifacts/example_1.handlebars'));
var expected = 'foo\n';
var result = template({foo: 'foo'});
var result = template({ foo: 'foo' });
equal(result, expected);
});
@@ -15,7 +15,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
equal(template, require('./artifacts/example_2.hbs'));
var expected = 'Hello, World!\n';
var result = template({name: 'World'});
var result = template({ name: 'World' });
equal(result, expected);
});
+75 -43
View File
@@ -1,34 +1,58 @@
describe('runtime', function() {
describe('#template', function() {
it('should throw on invalid templates', function() {
shouldThrow(function() {
Handlebars.template({});
}, Error, 'Unknown template object: object');
shouldThrow(function() {
Handlebars.template();
}, Error, 'Unknown template object: undefined');
shouldThrow(function() {
Handlebars.template('');
}, Error, 'Unknown template object: string');
shouldThrow(
function() {
Handlebars.template({});
},
Error,
'Unknown template object: object'
);
shouldThrow(
function() {
Handlebars.template();
},
Error,
'Unknown template object: undefined'
);
shouldThrow(
function() {
Handlebars.template('');
},
Error,
'Unknown template object: string'
);
});
it('should throw on version mismatch', function() {
shouldThrow(function() {
Handlebars.template({
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: {}
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
},
Error,
/Template was precompiled with a newer version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {}
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
});
});
@@ -38,34 +62,42 @@ describe('runtime', function() {
}
it('should throw for depthed methods without depths', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
}, Error, 'must pass parent depths');
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass parent depths'
);
});
it('should throw for block param methods without params', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
}, Error, 'must pass block params');
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass block params'
);
});
it('should expose child template', function() {
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
// Calling twice to hit the non-compiled case.
// Calling twice to hit the non-compiled case.
equal(template._child(1)(), 'bar');
equal(template._child(1)(), 'bar');
});
it('should render depthed content', function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
equal(template._child(1, undefined, [], [{bar: 'baz'}])(), 'baz');
// Calling twice to hit the non-compiled case.
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
});
});
+196 -132
View File
@@ -1,144 +1,208 @@
describe('security issues', function() {
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
it('should not allow constructors to be accessed', function() {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
it('should not allow constructors to be accessed', function() {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{constructor.name}}')
.withInput({})
.toCompileTo('');
});
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
.withInput({})
.withHelper('list', function(element) {
return [element];
})
.toCompileTo('');
});
it('should allow the "constructor" property to be accessed if it is enumerable', 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');
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
'name': 'here we go'
}}, 'here we go');
});
it('should allow prototype properties that are not constructors', function() {
function TestClass() {
}
Object.defineProperty(TestClass.prototype, 'abc', {
get: function() {
return 'xyz';
}
});
shouldCompileTo('{{#with this as |obj|}}{{obj.abc}}{{/with}}',
new TestClass(), 'xyz');
shouldCompileTo('{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}',
new TestClass(), 'xyz');
});
expectTemplate('{{constructor.name}}')
.withInput({})
.toCompileTo('');
});
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
.withInput({})
.withHelper('list', function(element) {
return [element];
})
.toCompileTo('');
});
it('should allow the "constructor" property to be accessed if it is enumerable', 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'
);
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
});
it('should allow prototype properties that are not constructors', function() {
function TestClass() {}
Object.defineProperty(TestClass.prototype, 'abc', {
get: function() {
return 'xyz';
}
});
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }});
}).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({ fn: function() { return 'functionInData';}});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', 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" .}}');
template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true});
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
});
shouldCompileTo(
'{{#with this as |obj|}}{{obj.abc}}{{/with}}',
new TestClass(),
'xyz'
);
shouldCompileTo(
'{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}',
new TestClass(),
'xyz'
);
});
});
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
}
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({
fn: function() {
functionCalls.push('called');
}
expectTemplate('{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}')
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595', function() {
it('properties, that are required to be enumerable', function() {
expectTemplate('{{constructor}}').withInput({}).toCompileTo('');
expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__proto__}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo('');
});
}).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({
fn: function() {
return 'functionInData';
}
});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', 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" .}}');
template(
{
fn: function() {
functionCalls.push('called');
}
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile(
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
});
});
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
}
expectTemplate(
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}'
)
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595', function() {
it('properties, that are required to be enumerable', function() {
expectTemplate('{{constructor}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__defineGetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__defineSetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__lookupGetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__proto__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "constructor"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__defineGetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__defineSetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__proto__"}}')
.withInput({})
.toCompileTo('');
});
});
});
+15 -9
View File
@@ -1,7 +1,7 @@
try {
if (typeof define !== 'function' || !define.amd) {
var SourceMap = require('source-map'),
SourceMapConsumer = SourceMap.SourceMapConsumer;
SourceMapConsumer = SourceMap.SourceMapConsumer;
}
} catch (err) {
/* NOP for in browser */
@@ -13,22 +13,28 @@ describe('source-map', function() {
}
it('should safely include source map info', function() {
var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
var template = Handlebars.precompile('{{hello}}', {
destName: 'dest.js',
srcName: 'src.hbs'
});
equal(!!template.code, true);
equal(!!template.map, !CompilerContext.browser);
});
it('should map source properly', function() {
var templateSource = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {destName: 'dest.js', srcName: 'src.hbs'});
var templateSource =
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {
destName: 'dest.js',
srcName: 'src.hbs'
});
if (template.map) {
var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
var mapped = consumer.originalPositionFor(generated);
equal(mapped.line, source.line);
+26 -14
View File
@@ -5,11 +5,11 @@ describe('spec', function() {
}
var _ = require('underscore'),
fs = require('fs');
fs = require('fs');
var specDir = __dirname + '/mustache/specs/';
var specs = _.filter(fs.readdirSync(specDir), function(name) {
return (/.*\.json$/).test(name);
return /.*\.json$/.test(name);
});
_.each(specs, function(name) {
@@ -17,16 +17,17 @@ describe('spec', function() {
_.each(spec.tests, function(test) {
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec
// We also do not support alternative delimeters
if (name === '~lambdas.json'
// We also choose to throw if paritals are not found
|| (name === 'partials.json' && test.name === 'Failed Lookup')
// We nest the entire response from partials, not just the literals
|| (name === 'partials.json' && test.name === 'Standalone Indentation')
|| (/\{\{=/).test(test.template)
|| _.any(test.partials, function(partial) { return (/\{\{=/).test(partial); })) {
if (
name === '~lambdas.json' ||
// We also choose to throw if paritals are not found
(name === 'partials.json' && test.name === 'Failed Lookup') ||
// We nest the entire response from partials, not just the literals
(name === 'partials.json' && test.name === 'Standalone Indentation') ||
/\{\{=/.test(test.template) ||
_.any(test.partials, function(partial) {
return /\{\{=/.test(partial);
})
) {
it.skip(name + ' - ' + test.name);
return;
}
@@ -40,9 +41,20 @@ describe('spec', function() {
}
it(name + ' - ' + test.name, function() {
if (test.partials) {
shouldCompileToWithPartials(test.template, [data, {}, test.partials, true], true, test.expected, test.desc + ' "' + test.template + '"');
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 + '"');
shouldCompileTo(
test.template,
[data, {}, {}, true],
test.expected,
test.desc + ' "' + test.template + '"'
);
}
});
});
+124 -53
View File
@@ -3,43 +3,58 @@ 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});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', { strict: true });
template({});
}, Exception, /"hello" not defined in/);
template({});
},
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');
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({ hello: { bar: 'foo' } }), 'foo');
shouldThrow(function() {
template({hello: {}});
}, Exception, /"bar" not defined in/);
shouldThrow(
function() {
template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
});
it('should handle explicit undefined', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({hello: {bar: undefined}}), '');
equals(template({ hello: { bar: undefined } }), '');
});
it('should error on missing property lookup in known helpers mode', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', {
strict: true,
knownHelpersOnly: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true});
var template = CompilerContext.compile('{{hello}}', { strict: true });
template();
}, Error);
});
it('should error on missing data lookup', function() {
var template = CompilerContext.compile('{{@hello}}', {strict: true});
equals(template(undefined, {data: {hello: 'foo'}}), 'foo');
var template = CompilerContext.compile('{{@hello}}', { strict: true });
equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
shouldThrow(function() {
template();
@@ -47,45 +62,81 @@ describe('strict', function() {
});
it('should not run helperMissing for helper calls', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello foo}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello foo}}', {
strict: true
});
template({foo: true});
}, Exception, /"hello" not defined in/);
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
strict: true
});
template({foo: true});
}, Exception, /"hello" not defined in/);
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
});
it('should throw on ambiguous blocks', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
strict: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
strict: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile(
'{{#hello.bar}}{{/hello.bar}}',
{ strict: true }
);
template({hello: {}});
}, Exception, /"bar" not defined in/);
template({ hello: {} });
},
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});
var template = CompilerContext.compile(
'{{#unless foo}}success{{/unless}}',
{ strict: true }
);
equals(template({}), 'success');
});
it('should allow undefined hash when passed to helpers', function() {
var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
var template = CompilerContext.compile('{{helper value=@foo}}', {
strict: true
});
var helpers = {
helper: function(options) {
equals('value' in options.hash, true);
@@ -93,19 +144,27 @@ describe('strict', function() {
return 'success';
}
};
equals(template({}, {helpers: helpers}), 'success');
equals(template({}, { helpers: helpers }), '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');
shouldThrow(
function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
},
Exception,
'"hello" not defined in [object Object] - 4:5'
);
});
it('should error contains correct location properties on missing property lookup', function() {
try {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
} catch (error) {
equals(error.lineNumber, 4);
@@ -118,25 +177,33 @@ describe('strict', function() {
describe('assume objects', function() {
it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
equal(template({}), '');
});
it('should ignore missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
equal(template({hello: {}}), '');
equal(template({ hello: {} }), '');
});
it('should error on missing object', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
template({});
}, Error);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
template();
}, Error);
@@ -144,14 +211,18 @@ describe('strict', function() {
it('should error on missing data lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{@hello.bar}}', {
assumeObjects: true
});
template();
}, Error);
});
it('should execute blockHelperMissing', function() {
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
assumeObjects: true
});
equals(template({}), 'foo');
});
+108 -38
View File
@@ -1,6 +1,8 @@
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 template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
stringParams: true
});
var helpers = {
wycats: function(passiveVoice, noun) {
@@ -8,57 +10,99 @@ describe('string params mode', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'HELP ME MY BOSS is.a slave.driver', 'String parameters output');
equals(
result,
'HELP ME MY BOSS is.a slave.driver',
'String parameters output'
);
});
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 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);
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
': ' +
options.fn(this)
);
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'HELP ME MY BOSS is.a slave.driver: help :(', 'String parameters output');
equals(
result,
'HELP ME MY BOSS is.a slave.driver: help :(',
'String parameters output'
);
});
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 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;
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun
);
},
'with': function(context, options) {
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
var result = template(
{
dale: {},
need: 'need-a'
}, {helpers: helpers});
need: 'need-a'
},
{ helpers: helpers }
);
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', 'Proper context variable output');
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
'Proper context variable output'
);
});
it('information about the types is passed along', function() {
var template = CompilerContext.compile("{{tomdale 'need' dad.joke true false}}", { stringParams: true });
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(
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');
@@ -72,7 +116,10 @@ describe('string params mode', function() {
});
it('hash parameters get type information', function() {
var template = CompilerContext.compile("{{tomdale he.says desire='need' noun=dad.joke bool=true}}", { stringParams: true });
var template = CompilerContext.compile(
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
{ stringParams: true }
);
var helpers = {
tomdale: function(exclamation, options) {
@@ -94,9 +141,12 @@ describe('string params mode', function() {
});
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 template = CompilerContext.compile(
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
{ stringParams: true }
);
var context = {dale: {}};
var context = { dale: {} };
var helpers = {
tomdale: function(exclamation, options) {
@@ -110,7 +160,7 @@ describe('string params mode', function() {
equal(options.hash.bool, true);
return 'Helper called';
},
'with': function(withContext, options) {
with: function(withContext, options) {
return options.fn(options.contexts[0][withContext]);
}
};
@@ -120,34 +170,52 @@ describe('string params mode', function() {
});
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 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);
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun +
' ' +
options.fn(this)
);
},
'with': function(context, options) {
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
var result = template(
{
dale: {},
need: 'need-a'
}, {helpers: helpers});
need: 'need-a'
},
{ helpers: helpers }
);
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', 'Proper context variable output');
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
'Proper context variable output'
);
});
it('with nested block ambiguous', function() {
var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
var template = CompilerContext.compile(
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
{ stringParams: true }
);
var helpers = {
'with': function() {
with: function() {
return 'WITH';
},
view: function() {
@@ -155,12 +223,14 @@ describe('string params mode', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'WITH');
});
it('should handle DATA', function() {
var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true });
var template = CompilerContext.compile('{{foo @bar}}', {
stringParams: true
});
var helpers = {
foo: function(bar, options) {
+75 -21
View File
@@ -31,7 +31,7 @@ describe('subexpressions', function() {
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 context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
var helpers = {
blog: function(val, that, theOther) {
return 'val is ' + val + ', ' + that + ' and ' + theOther;
@@ -59,7 +59,7 @@ describe('subexpressions', function() {
});
it('GH-800 : Complex subexpressions', function() {
var context = {a: 'a', b: 'b', c: {c: 'c'}, d: 'd', e: {e: 'e'}};
var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } };
var helpers = {
dash: function(a, b) {
return a + '-' + b;
@@ -69,7 +69,11 @@ describe('subexpressions', function() {
}
};
shouldCompileTo("{{dash 'abc' (concat a b)}}", [context, helpers], 'abc-ab');
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');
@@ -122,24 +126,36 @@ describe('subexpressions', function() {
});
it('multiple subexpressions in a hash', function() {
var string = '{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
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('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
return new Handlebars.SafeString(
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(string, [{}, helpers], '<input aria-label="Name" placeholder="Example User" />');
shouldCompileTo(
string,
[{}, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
});
it('multiple subexpressions in a hash with context', function() {
var string = '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var string =
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var context = {
item: {
@@ -153,44 +169,82 @@ describe('subexpressions', function() {
var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
return new Handlebars.SafeString(
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(string, [context, helpers], '<input aria-label="Name" placeholder="Example User" />');
shouldCompileTo(
string,
[context, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
});
it('in string params mode,', function() {
var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
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');
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');
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;
}
};
var result = template({
foo: {},
yeah: {}
}, {helpers: helpers});
var result = template(
{
foo: {},
yeah: {}
},
{ helpers: helpers }
);
equals(result, 'fooyeah');
});
it('as hashes in string params mode', function() {
var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
var template = CompilerContext.compile('{{blog fun=(bork)}}', {
stringParams: true
});
var helpers = {
blog: function(options) {
@@ -202,7 +256,7 @@ describe('subexpressions', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'val is BORK');
});
+395 -48
View File
@@ -15,18 +15,18 @@ describe('Tokenizer', function() {
function tokenize(template) {
var parser = Handlebars.Parser,
lexer = parser.lexer;
lexer = parser.lexer;
lexer.setInput(template);
var out = [],
token;
token;
while ((token = lexer.lex())) {
var result = parser.terminals_[token] || token;
if (!result || result === 'EOF' || result === 'INVALID') {
break;
}
out.push({name: result, text: lexer.yytext});
out.push({ name: result, text: lexer.yytext });
}
return out;
@@ -55,7 +55,16 @@ describe('Tokenizer', function() {
it('supports escaping delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -63,7 +72,14 @@ describe('Tokenizer', function() {
it('supports escaping multiple delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -72,14 +88,35 @@ describe('Tokenizer', function() {
it('supports escaping a triple stash', function() {
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
});
it('supports escaping escape character', function() {
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -87,7 +124,19 @@ describe('Tokenizer', function() {
it('supports escaping multiple escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -97,7 +146,18 @@ describe('Tokenizer', function() {
it('supports escaped mustaches after escaped escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[4], 'OPEN', '{{');
@@ -108,7 +168,17 @@ describe('Tokenizer', function() {
it('supports escaped escape characters after escaped mustaches', function() {
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
shouldBeToken(result[5], 'CONTENT', '\\');
@@ -118,7 +188,19 @@ describe('Tokenizer', function() {
it('supports escaped escape character on a triple stash', function() {
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN_UNESCAPED',
'ID',
'CLOSE_UNESCAPED',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -133,7 +215,15 @@ describe('Tokenizer', function() {
var result = tokenize('{{foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows path literals with []', function() {
@@ -143,7 +233,18 @@ describe('Tokenizer', function() {
it('allows multiple path literals on a line with []', function() {
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE',
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows escaped literals in []', function() {
@@ -158,13 +259,29 @@ describe('Tokenizer', function() {
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
var result = tokenize('{{../foo/bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
it('tokenizes a path with .. as a parent path', function() {
var result = tokenize('{{../foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
@@ -216,7 +333,15 @@ describe('Tokenizer', function() {
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo/bar.baz }}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_PARTIAL',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('tokenizes partial block declarations', function() {
@@ -225,34 +350,69 @@ describe('Tokenizer', function() {
});
it('tokenizes a comment as "COMMENT"', function() {
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
});
it('tokenizes a block comment as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
});
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
var result = tokenize(
'foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'
);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
});
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
var result = tokenize('{{#foo}}content{{/foo}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
});
it('tokenizes directives', function() {
shouldMatchTokens(
tokenize('{{#*foo}}content{{/foo}}'),
['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
shouldMatchTokens(
tokenize('{{*foo}}'),
['OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes inverse sections as "INVERSE"', function() {
@@ -351,28 +511,98 @@ describe('Tokenizer', function() {
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize('{{ foo bar baz=bat }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'NUMBER',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=true }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=false }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar\n baz=bat }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" bam=wot }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{foo omg bar=baz bat="bam"}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
shouldBeToken(result[2], 'ID', 'omg');
});
@@ -386,7 +616,15 @@ describe('Tokenizer', function() {
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{ foo bar=@baz }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'EQUALS',
'DATA',
'ID',
'CLOSE'
]);
shouldBeToken(result[5], 'ID', 'baz');
});
@@ -400,12 +638,27 @@ describe('Tokenizer', function() {
it('tokenizes subexpressions', function() {
var result = tokenize('{{foo (bar)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{foo (a-x b-y)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'a-x');
shouldBeToken(result[4], 'ID', 'b-y');
@@ -413,7 +666,21 @@ describe('Tokenizer', function() {
it('tokenizes nested subexpressions', function() {
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[3], 'ID', 'bar');
shouldBeToken(result[5], 'ID', 'lol');
shouldBeToken(result[6], 'ID', 'rofl');
@@ -421,29 +688,109 @@ describe('Tokenizer', function() {
});
it('tokenizes nested subexpressions: literals', function() {
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
var result = tokenize(
'{{foo (bar (lol true) false) (baz 1) (blah \'b\') (blorg "c")}}'
);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'BOOLEAN',
'CLOSE_SEXPR',
'BOOLEAN',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'NUMBER',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'CLOSE'
]);
});
it('tokenizes block params', function() {
var result = tokenize('{{#foo as |bar|}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as |bar baz|}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as | bar baz |}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as as | bar baz |}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{else foo as |bar baz|}}');
shouldMatchTokens(result, ['OPEN_INVERSE_CHAIN', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_INVERSE_CHAIN',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
});
it('tokenizes raw blocks', function() {
var result = tokenize('{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}');
shouldMatchTokens(result, ['OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK', 'CONTENT', 'OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK']);
var result = tokenize(
'{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'
);
shouldMatchTokens(result, [
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK',
'CONTENT',
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK'
]);
});
});
+244 -54
View File
@@ -1,7 +1,7 @@
describe('track ids', function() {
var context;
beforeEach(function() {
context = {is: {a: 'foo'}, slave: {driver: 'bar'}};
context = { is: { a: 'foo' }, slave: { driver: 'bar' } };
});
it('should not include anything without the flag', function() {
@@ -16,38 +16,70 @@ describe('track ids', function() {
}
};
equals(template({}, {helpers: helpers}), 'success');
equals(template({}, { helpers: helpers }), 'success');
});
it('should include argument ids', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {trackIds: true});
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;
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');
equals(
template(context, { helpers: helpers }),
'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 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;
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');
equals(
template(context, { helpers: helpers }),
'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 template = CompilerContext.compile(
'{{wycats ./is.a ../slave.driver this.is.a this}}',
{ trackIds: true }
);
var helpers = {
wycats: function(passiveVoice, noun, thiz, thiz2, options) {
@@ -56,29 +88,57 @@ describe('track ids', function() {
equal(options.ids[2], 'is.a');
equal(options.ids[3], '');
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
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');
equals(
template(context, { helpers: helpers }),
'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 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;
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');
equals(
template({}, { helpers: helpers, data: context }),
'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 template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
@@ -86,17 +146,31 @@ describe('track ids', function() {
equal(options.ids[1], null);
equal(options.hashIds.key, null);
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun + ' ' + options.hash.key;
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
' ' +
options.hash.key
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1 foo false');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS 1 foo false'
);
});
it('should return true for subexpressions', function() {
var template = CompilerContext.compile('{{wycats (sub)}}', {trackIds: true});
var template = CompilerContext.compile('{{wycats (sub)}}', {
trackIds: true
});
var helpers = {
sub: function() { return 1; },
sub: function() {
return 1;
},
wycats: function(passiveVoice, options) {
equal(options.ids[0], true);
@@ -104,28 +178,43 @@ describe('track ids', function() {
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
equals(template(context, { helpers: helpers }), '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 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});
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;
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');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS zomg.a:foo slave.driver:bar'
);
});
describe('builtin helpers', function() {
@@ -140,53 +229,119 @@ 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});
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');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'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});
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');
equals(
template(
{ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } },
{ helpers: helpers }
),
'foo:object.foo\nbar:object.bar\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true});
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');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'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});
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');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'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});
var template = CompilerContext.compile(
'{{#with field}}{{wycats name}}{{/with}}',
{ trackIds: true }
);
equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true});
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');
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
describe('#blockHelperMissing', function() {
it('should track contextPath for arrays', function() {
var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n');
equals(
template({ field: [{ name: 'foo' }] }, { helpers: helpers }),
'foo:field.0\n'
);
});
it('should track contextPath for keys', function() {
var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}',
{ trackIds: true }
);
equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n');
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
});
@@ -202,36 +357,71 @@ describe('track ids', function() {
};
it('should pass track id for basic partial', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude}}{{/dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n'
);
});
it('should pass track id for context partial', function() {
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {
trackIds: true
}),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{#each this}}{{wycats name}}{{/each}}', {trackIds: true})
dude: CompilerContext.compile(
'{{#each this}}{{wycats name}}{{/each}}',
{ trackIds: true }
)
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n'
);
});
it('should invalidate context for partials with parameters', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:true\nAlan:true\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:true\nAlan:true\n'
);
});
});
});
+16 -5
View File
@@ -5,19 +5,30 @@ describe('utils', function() {
if (!(safe instanceof Handlebars.SafeString)) {
throw new Error('Must be instance of SafeString');
}
equals(safe.toString(), 'testing 1, 2, 3', 'SafeString is equivalent to its underlying string');
equals(
safe.toString(),
'testing 1, 2, 3',
'SafeString is equivalent to its underlying string'
);
});
it('it should not escape SafeString properties', function() {
var name = new Handlebars.SafeString('<em>Sean O&#x27;Malley</em>');
shouldCompileTo('{{name}}', [{name: name}], '<em>Sean O&#x27;Malley</em>');
shouldCompileTo(
'{{name}}',
[{ name: name }],
'<em>Sean O&#x27;Malley</em>'
);
});
});
describe('#escapeExpression', function() {
it('shouhld escape html', function() {
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo&lt;&amp;&quot;&#x27;&gt;');
equals(
Handlebars.Utils.escapeExpression('foo<&"\'>'),
'foo&lt;&amp;&quot;&#x27;&gt;'
);
equals(Handlebars.Utils.escapeExpression('foo='), 'foo&#x3D;');
});
it('should not escape SafeString', function() {
@@ -58,7 +69,7 @@ describe('utils', function() {
equals(Handlebars.Utils.isEmpty(0), false);
equals(Handlebars.Utils.isEmpty([1]), false);
equals(Handlebars.Utils.isEmpty('foo'), false);
equals(Handlebars.Utils.isEmpty({bar: 1}), false);
equals(Handlebars.Utils.isEmpty({ bar: 1 }), false);
});
});
@@ -69,7 +80,7 @@ describe('utils', function() {
}
A.prototype.b = 4;
var b = {b: 2};
var b = { b: 2 };
Handlebars.Utils.extend(b, new A());
+46 -24
View File
@@ -7,7 +7,11 @@ describe('Visitor', function() {
// Simply run the thing and make sure it does not fail and that all of the
// stub methods are executed
var visitor = new Handlebars.Visitor();
visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'));
visitor.accept(
Handlebars.parse(
'{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'
)
);
visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{* bar }}'));
@@ -40,7 +44,11 @@ describe('Visitor', function() {
equal(comment.value, 'comment');
};
visitor.accept(Handlebars.parse('{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'));
visitor.accept(
Handlebars.parse(
'{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'
)
);
});
describe('mutating', function() {
@@ -50,12 +58,15 @@ describe('Visitor', function() {
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return {type: 'NumberLiteral', value: 42, loc: string.loc};
return { type: 'NumberLiteral', value: 42, loc: string.loc };
};
var ast = Handlebars.parse('{{foo foo="foo"}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should treat undefined resonse as identity', function() {
var visitor = new Handlebars.Visitor();
@@ -63,7 +74,10 @@ describe('Visitor', function() {
var ast = Handlebars.parse('{{foo foo=42}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should remove false responses', function() {
var visitor = new Handlebars.Visitor();
@@ -78,30 +92,38 @@ describe('Visitor', function() {
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
});
it('should throw when removing required values', function() {
shouldThrow(function() {
var visitor = new Handlebars.Visitor();
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return false;
};
visitor.mutating = true;
visitor.PathExpression = function() {
return false;
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
}, Handlebars.Exception, 'MustacheStatement requires path');
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'MustacheStatement requires path'
);
});
it('should throw when returning non-node responses', function() {
shouldThrow(function() {
var visitor = new Handlebars.Visitor();
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return {};
};
visitor.mutating = true;
visitor.PathExpression = function() {
return {};
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
}, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting path on MustacheStatement');
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'Unexpected node type "undefined" found when accepting path on MustacheStatement'
);
});
});
describe('arrays', function() {
@@ -110,7 +132,7 @@ describe('Visitor', function() {
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return {type: 'NumberLiteral', value: 42, loc: string.locInfo};
return { type: 'NumberLiteral', value: 42, loc: string.locInfo };
};
var ast = Handlebars.parse('{{foo "foo"}}');
+64 -15
View File
@@ -1,6 +1,6 @@
describe('whitespace control', function() {
it('should strip whitespace around mustache calls', function() {
var hash = {foo: 'bar<'};
var hash = { foo: 'bar<' };
shouldCompileTo(' {{~foo~}} ', hash, 'bar&lt;');
shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
@@ -14,15 +14,23 @@ describe('whitespace control', function() {
describe('blocks', function() {
it('should strip whitespace around simple block calls', function() {
var hash = {foo: 'bar<'};
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 ');
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 ');
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 '
);
});
it('should strip whitespace around inverse block calls', function() {
var hash = {};
@@ -32,10 +40,14 @@ describe('whitespace control', function() {
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
shouldCompileTo(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
shouldCompileTo(
' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
hash,
'bar'
);
});
it('should strip whitespace around complex block calls', function() {
var hash = {foo: 'bar<'};
var hash = { foo: 'bar<' };
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
@@ -44,8 +56,16 @@ describe('whitespace control', function() {
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, '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<');
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<'
);
hash = {};
@@ -56,21 +76,50 @@ describe('whitespace control', function() {
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'baz');
shouldCompileTo(
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'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 {{~> 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');
shouldCompileToWithPartials(
'foo\n {{~> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo\n {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo\n bar'
);
});
it('should only strip whitespace once', function() {
var hash = {foo: 'bar'};
var hash = { foo: 'bar' };
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
});