Files
handlebars.js/spec/regressions.js
T
2019-12-03 22:39:17 +01:00

491 lines
14 KiB
JavaScript

describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() {
var data = {
books: [
{
title: 'The origin of species',
author: {
name: 'Charles Darwin'
}
},
{
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'
);
});
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"
);
});
it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}';
var data = { arr: [1, 2] };
shouldCompileTo(string, data, '1, 2', 'it works as expected');
});
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 data = {
thing: function() {
return 'blah';
},
things: [
{ 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';
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' } }
];
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'
);
});
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 helpers = {
blk: function(block) {
return block.fn('');
},
inverse: function(block) {
return block.inverse('');
}
};
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
});
it('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
});
it('GH-375: Unicode line terminators', function() {
shouldCompileTo('\u2028', {}, '\u2028');
});
it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */
Object.prototype[0xd834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xd834];
/* eslint-enable no-extend-native */
});
it('GH-437: Matching escaping', function() {
shouldThrow(function() {
CompilerContext.compile('{{{a}}');
}, Error);
shouldThrow(function() {
CompilerContext.compile('{{a}}}');
}, Error);
});
it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}';
var data = { arr: [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
});
it('Mustache man page', function() {
var string =
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
var data = {
name: 'Chris',
value: 10000,
taxed_value: 10000 - 10000 * 0.4,
in_ca: true
};
shouldCompileTo(
string,
data,
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
'the hello world mustache example works'
);
});
it('GH-731: zero context rendering', function() {
shouldCompileTo(
'{{#foo}} This is {{bar}} ~ {{/foo}}',
{ foo: 0, bar: 'OK' },
' This is ~ '
);
});
it('GH-820: zero pathed rendering', function() {
shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
});
it('GH-837: undefined values for helpers', function() {
var helpers = {
str: function(value) {
return value + '';
}
};
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
});
it('GH-926: Depths and de-dupe', function() {
var context = {
name: 'foo',
data: [1],
notData: [1]
};
var template = CompilerContext.compile(
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
);
var result = template(context);
equals(result, 'foo');
});
it('GH-1021: Each empty string key', function() {
var data = {
'': 'foo',
name: 'Chris',
value: 10000
};
shouldCompileTo(
'{{#each data}}Key: {{@key}}\n{{/each}}',
{ data: data },
'Key: \nKey: name\nKey: value\n'
);
});
it('GH-1054: Should handle simple safe string responses', function() {
var root = '{{#wrap}}{{>partial}}{{/wrap}}';
var partials = {
partial: '{{#wrap}}<partial>{{/wrap}}'
};
var helpers = {
wrap: function(options) {
return new Handlebars.SafeString(options.fn());
}
};
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'
);
});
it('GH-1093: Undefined helper context', function() {
var obj = { foo: undefined, bar: 'bat' };
var helpers = {
helper: function() {
// It's valid to execute a block against an undefined context, but
// helpers can not do so, so we expect to have an empty object here;
for (var name in this) {
if (Object.prototype.hasOwnProperty.call(this, name)) {
return 'found';
}
}
// And to make IE happy, check for the known string as length is not enumerated.
return this === 'bat' ? 'found' : 'not';
}
};
shouldCompileTo(
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
[{ obj: obj }, helpers],
'notfoundbat'
);
});
it('should support multiple levels of inline partials', function() {
var string =
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
var string = '{{#> layout}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}';
var partials = {
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
inner: ''
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
});
it('GH-1135 : Context handling within each iteration', function() {
var obj = { array: [1], name: 'John' };
var helpers = {
myif: function(conditional, options) {
if (conditional) {
return options.fn(this);
} else {
return options.inverse(this);
}
}
};
shouldCompileTo(
'{{#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}}';
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
});
it('should allow hash with protected array names', function() {
var obj = { array: [1], name: 'John' };
var helpers = {
helpa: function(options) {
return options.hash.length;
}
};
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
});
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 ',
''
);
});
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
var string = 'template {{>partial}} template';
var partials = {
partialWithBlock:
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'template block partial block template'
);
});
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();
registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase();
});
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"'
);
});
// 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
});
}
});
it('should allow hash with protected array names', function() {
var obj = { array: [1], name: 'John' };
var helpers = {
helpa: function(options) {
return options.hash.length;
}
};
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
});
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
// Do not run test for runs without compiler
if (!Handlebars.compile) {
return;
}
var newHandlebarsInstance;
beforeEach(function() {
newHandlebarsInstance = Handlebars.create();
});
afterEach(function() {
sinon.restore();
});
it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({
dude: 'I am a partial'
});
var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
newHandlebarsInstance.compile(string)(); // This should only compile template
equal(templateSpy.callCount, 3);
sinon.restore();
});
});
});