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
+81 -31
View File
@@ -1,32 +1,42 @@
/* eslint-disable no-process-env */ /* eslint-disable no-process-env */
module.exports = function(grunt) { module.exports = function(grunt) {
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js', 'integration-testing/**/node_modules'], clean: [
'tmp',
'dist',
'lib/handlebars/compiler/parser.js',
'integration-testing/**/node_modules'
],
copy: { copy: {
dist: { dist: {
options: { options: {
processContent: function(content) { processContent: function(content) {
return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n') return (
+ content; grunt.template.process(
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
) + content
);
} }
}, },
files: [ files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
]
}, },
cdnjs: { cdnjs: {
files: [ files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'} { expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
] ]
}, },
components: { components: {
files: [ files: [
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'}, {
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'} expand: true,
cwd: 'components/',
src: ['**'],
dest: 'dist/components'
},
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
] ]
} }
}, },
@@ -41,24 +51,28 @@ module.exports = function(grunt) {
options: { options: {
modules: 'amd' modules: 'amd'
}, },
files: [{ files: [
{
expand: true, expand: true,
cwd: 'lib/', cwd: 'lib/',
src: '**/!(index).js', src: '**/!(index).js',
dest: 'dist/amd/' dest: 'dist/amd/'
}] }
]
}, },
cjs: { cjs: {
options: { options: {
modules: 'common' modules: 'common'
}, },
files: [{ files: [
{
cwd: 'lib/', cwd: 'lib/',
expand: true, expand: true,
src: '**/!(index).js', src: '**/!(index).js',
dest: 'dist/cjs/' dest: 'dist/cjs/'
}] }
]
} }
}, },
webpack: { webpack: {
@@ -67,7 +81,12 @@ module.exports = function(grunt) {
module: { module: {
loaders: [ loaders: [
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it. // the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next' } {
test: /\.jsx?$/,
exclude: /node_modules/,
loader:
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
}
] ]
}, },
output: { output: {
@@ -116,7 +135,8 @@ module.exports = function(grunt) {
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/ preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
}, },
dist: { dist: {
files: [{ files: [
{
cwd: 'dist/', cwd: 'dist/',
expand: true, expand: true,
src: ['handlebars*.js', '!*.min.js'], src: ['handlebars*.js', '!*.min.js'],
@@ -124,7 +144,8 @@ module.exports = function(grunt) {
rename: function(dest, src) { rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js'); return dest + src.replace(/\.js$/, '.min.js');
} }
}] }
]
} }
}, },
@@ -148,28 +169,41 @@ module.exports = function(grunt) {
all: { all: {
options: { options: {
build: process.env.TRAVIS_JOB_ID, build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'], urls: [
'http://localhost:9999/spec/?headless=true',
'http://localhost:9999/spec/amd.html?headless=true'
],
detailedError: true, detailedError: true,
concurrency: 4, concurrency: 4,
browsers: [ browsers: [
{browserName: 'chrome'}, { browserName: 'chrome' },
{browserName: 'firefox', platform: 'Linux'}, { browserName: 'firefox', platform: 'Linux' },
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'}, // {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
// {browserName: 'safari', version: 8, platform: 'OS X 10.10'}, // {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, {
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'} browserName: 'internet explorer',
version: 11,
platform: 'Windows 8.1'
},
{
browserName: 'internet explorer',
version: 10,
platform: 'Windows 8'
}
] ]
} }
}, },
sanity: { sanity: {
options: { options: {
build: process.env.TRAVIS_JOB_ID, build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'], urls: [
'http://localhost:9999/spec/umd.html?headless=true',
'http://localhost:9999/spec/amd-runtime.html?headless=true',
'http://localhost:9999/spec/umd-runtime.html?headless=true'
],
detailedError: true, detailedError: true,
concurrency: 2, concurrency: 2,
browsers: [ browsers: [{ browserName: 'chrome' }]
{browserName: 'chrome'}
]
} }
} }
}, },
@@ -180,7 +214,6 @@ module.exports = function(grunt) {
bg: false, bg: false,
fail: true fail: true
} }
}, },
watch: { watch: {
@@ -231,13 +264,30 @@ module.exports = function(grunt) {
this.registerTask('amd', ['babel:amd', 'requirejs']); this.registerTask('amd', ['babel:amd', 'requirejs']);
grunt.registerTask('bench', ['metrics']); grunt.registerTask('bench', ['metrics']);
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []); grunt.registerTask(
'sauce',
process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []
);
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml // Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
grunt.registerTask('extensive-tests-and-publish-to-aws', ['default', 'bgShell:integrationTests', 'sauce', 'metrics', 'publish:latest']); grunt.registerTask('extensive-tests-and-publish-to-aws', [
grunt.registerTask('on-file-change', ['build', 'amd', 'concat:tests', 'test']); 'default',
'bgShell:integrationTests',
'sauce',
'metrics',
'publish:latest'
]);
grunt.registerTask('on-file-change', [
'build',
'amd',
'concat:tests',
'test'
]);
// === Primary tasks === // === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']); grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']); grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('integration-tests', ['default', 'bgShell:integrationTests']); grunt.registerTask('integration-tests', [
'default',
'bgShell:integrationTests'
]);
}; };
+8 -3
View File
@@ -6,7 +6,9 @@ module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'), var distFiles = fs.readdirSync('dist'),
distSizes = {}; distSizes = {};
async.each(distFiles, function(file, callback) { async.each(
distFiles,
function(file, callback) {
var content; var content;
try { try {
content = fs.readFileSync('dist/' + file); content = fs.readFileSync('dist/' + file);
@@ -32,7 +34,10 @@ module.exports = function(grunt, callback) {
}); });
}, },
function() { function() {
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)); grunt.log.writeln(
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
);
callback([distSizes]); callback([distSizes]);
}); }
);
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var metrics = fs.readdirSync(__dirname); var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) { metrics.forEach(function(metric) {
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) { if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
return; return;
} }
+7 -2
View File
@@ -9,11 +9,16 @@ module.exports = function(grunt, callback) {
_.each(templates, function(info, template) { _.each(templates, function(info, template) {
var src = info.handlebars, var src = info.handlebars,
compiled = Handlebars.precompile(src, {}), compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers}); knownHelpers = Handlebars.precompile(src, {
knownHelpersOnly: true,
knownHelpers: info.helpers
});
templateSizes[template] = compiled.length; templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length; templateSizes['knownOnly_' + template] = knownHelpers.length;
}); });
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)); grunt.log.writeln(
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
);
callback([templateSizes]); callback([templateSizes]);
}; };
+2 -1
View File
@@ -8,5 +8,6 @@ module.exports = {
bar: true bar: true
}, },
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}' handlebars:
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
}; };
+8 -1
View File
@@ -1,5 +1,12 @@
module.exports = { module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] }, context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{name}}{{/each}}', handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}', dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}', mustache: '{{#names}}{{name}}{{/names}}',
+8 -1
View File
@@ -1,4 +1,11 @@
module.exports = { module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] }, context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#names}}{{name}}{{/names}}' handlebars: '{{#names}}{{name}}{{/names}}'
}; };
+3 -3
View File
@@ -7,9 +7,9 @@ module.exports = {
}, },
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
items: [ items: [
{name: 'red', current: true, url: '#Red'}, { name: 'red', current: true, url: '#Red' },
{name: 'green', current: false, url: '#Green'}, { name: 'green', current: false, url: '#Green' },
{name: 'blue', current: false, url: '#Blue'} { name: 'blue', current: false, url: '#Blue' }
] ]
}, },
+8 -1
View File
@@ -1,4 +1,11 @@
module.exports = { module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] }, context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}' handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
}; };
+9 -1
View File
@@ -1,5 +1,13 @@
module.exports = { module.exports = {
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' }, context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
],
foo: 'bar'
},
handlebars: '{{#each names}}{{../foo}}{{/each}}', handlebars: '{{#each names}}{{../foo}}{{/each}}',
mustache: '{{#names}}{{foo}}{{/names}}', mustache: '{{#names}}{{foo}}{{/names}}',
eco: '<% for item in @names: %><%= @foo %><% end %>' eco: '<% for item in @names: %><%= @foo %><% end %>'
+13 -3
View File
@@ -1,6 +1,16 @@
module.exports = { module.exports = {
context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' }, context: {
handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}', names: [
{ bat: 'foo', name: ['Moe'] },
{ bat: 'foo', name: ['Larry'] },
{ bat: 'foo', name: ['Curly'] },
{ bat: 'foo', name: ['Shemp'] }
],
foo: 'bar'
},
handlebars:
'{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}', mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>' eco:
'<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var templates = fs.readdirSync(__dirname); var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) { templates.forEach(function(template) {
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) { if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
return; return;
} }
module.exports[RegExp.$1] = require('./' + RegExp.$1); module.exports[RegExp.$1] = require('./' + RegExp.$1);
+4 -1
View File
@@ -1,5 +1,8 @@
module.exports = { module.exports = {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] }, context: {
name: '1',
kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }]
},
partials: { partials: {
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' }, mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' } handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
+10 -2
View File
@@ -1,8 +1,16 @@
module.exports = { module.exports = {
context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] }, context: {
peeps: [
{ name: 'Moe', count: 15 },
{ name: 'Larry', count: 5 },
{ name: 'Curly', count: 1 }
]
},
partials: { partials: {
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' }, mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' } handlebars: {
variables: 'Hello {{name}}! You have {{count}} new messages.'
}
}, },
handlebars: '{{#each peeps}}{{>variables}}{{/each}}', handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
+5 -3
View File
@@ -1,7 +1,9 @@
module.exports = { module.exports = {
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } }, context: { person: { name: { bar: { baz: 'Larry' } }, age: 45 } },
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}', handlebars:
'{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}', dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>', eco:
'<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}' mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
}; };
+1 -2
View File
@@ -1,8 +1,7 @@
module.exports = { module.exports = {
context: {name: 'Mick', count: 30}, context: { name: 'Mick', count: 30 },
handlebars: 'Hello {{name}}! You have {{count}} new messages.', handlebars: 'Hello {{name}}! You have {{count}} new messages.',
dust: 'Hello {name}! You have {count} new messages.', dust: 'Hello {name}! You have {count} new messages.',
mustache: 'Hello {{name}}! You have {{count}} new messages.', mustache: 'Hello {{name}}! You have {{count}} new messages.',
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.' eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
}; };
+40 -16
View File
@@ -1,19 +1,27 @@
var _ = require('underscore'), var _ = require('underscore'),
runner = require('./util/template-runner'), runner = require('./util/template-runner'),
eco,
eco, dust, Handlebars, Mustache; dust,
Handlebars,
Mustache;
try { try {
dust = require('dustjs-linkedin'); dust = require('dustjs-linkedin');
} catch (err) { /* NOP */ } } catch (err) {
/* NOP */
}
try { try {
Mustache = require('mustache'); Mustache = require('mustache');
} catch (err) { /* NOP */ } } catch (err) {
/* NOP */
}
try { try {
eco = require('eco'); eco = require('eco');
} catch (err) { /* NOP */ } } catch (err) {
/* NOP */
}
function error() { function error() {
throw new Error('EWOT'); throw new Error('EWOT');
@@ -22,21 +30,28 @@ function error() {
function makeSuite(bench, name, template, handlebarsOnly) { function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree. // Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name, var templateName = name,
context = template.context, context = template.context,
partials = template.partials, partials = template.partials,
handlebarsOut, handlebarsOut,
compatOut, compatOut,
dustOut, dustOut,
ecoOut, ecoOut,
mustacheOut; mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}), var handlebar = Handlebars.compile(template.handlebars, { data: false }),
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}), compat = Handlebars.compile(template.handlebars, {
options = {helpers: template.helpers}; data: false,
_.each(template.partials && template.partials.handlebars, function(partial, partialName) { compat: true
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false})); }),
options = { helpers: template.helpers };
_.each(template.partials && template.partials.handlebars, function(
partial,
partialName
) {
Handlebars.registerPartial(
partialName,
Handlebars.compile(partial, { data: false })
);
}); });
handlebarsOut = handlebar(context, options); handlebarsOut = handlebar(context, options);
@@ -58,7 +73,9 @@ function makeSuite(bench, name, template, handlebarsOnly) {
dustOut = false; dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName)); dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dustOut = out; }); dust.render(templateName, context, function(err, out) {
dustOut = out;
});
bench('dust', function() { bench('dust', function() {
dust.render(templateName, context, function() {}); dust.render(templateName, context, function() {});
@@ -107,9 +124,16 @@ function makeSuite(bench, name, template, handlebarsOnly) {
b = b.replace(/\s/g, ''); b = b.replace(/\s/g, '');
if (handlebarsOut !== b) { if (handlebarsOut !== b) {
throw new Error('Template output mismatch: ' + name throw new Error(
+ '\n\nHandlebars: ' + handlebarsOut 'Template output mismatch: ' +
+ '\n\n' + lang + ': ' + b); name +
'\n\nHandlebars: ' +
handlebarsOut +
'\n\n' +
lang +
': ' +
b
);
} }
} }
+44 -17
View File
@@ -33,16 +33,21 @@ BenchWarmer.prototype = {
this.names.push(name); this.names.push(name);
} }
var first = this.first, suiteName = this.suiteName, self = this; var first = this.first,
suiteName = this.suiteName,
self = this;
this.first = false; this.first = false;
var bench = new Benchmark(fn, { var bench = new Benchmark(fn, {
name: this.suiteName + ': ' + name, name: this.suiteName + ': ' + name,
onComplete: function() { onComplete: function() {
if (first) { self.startLine(suiteName); } if (first) {
self.startLine(suiteName);
}
self.writeBench(bench); self.writeBench(bench);
self.currentBenches.push(bench); self.currentBenches.push(bench);
}, onError: function() { },
onError: function() {
self.errors[this.name] = this; self.errors[this.name] = this;
} }
}); });
@@ -75,10 +80,14 @@ BenchWarmer.prototype = {
}); });
print('\n'); print('\n');
var errors = false, prop, bench; var errors = false,
prop,
bench;
for (prop in self.errors) { for (prop in self.errors) {
if (Object.prototype.hasOwnProperty.call(self, prop) if (
&& self.errors[prop].error.message !== 'EWOT') { Object.prototype.hasOwnProperty.call(self, prop) &&
self.errors[prop].error.message !== 'EWOT'
) {
errors = true; errors = true;
break; break;
} }
@@ -107,23 +116,39 @@ BenchWarmer.prototype = {
}, },
scaleTimes: function() { scaleTimes: function() {
var scaled = this.scaled = {}; var scaled = (this.scaled = {});
_.each(this.times, function(times, name) { _.each(
var output = scaled[name] = {}; this.times,
function(times, name) {
var output = (scaled[name] = {});
_.each(times, function(time, lang) { _.each(
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2); times,
}, this); function(time, lang) {
}, this); output[lang] = (
((time - this.minimum) / (this.maximum - this.minimum)) *
100
).toFixed(2);
},
this
);
},
this
);
}, },
printHeader: function(title, winners) { printHeader: function(title, winners) {
var benchSize = 0, names = this.names, i, l; var benchSize = 0,
names = this.names,
i,
l;
for (i = 0, l = names.length; i < l; i++) { for (i = 0, l = names.length; i < l; i++) {
var name = names[i]; var name = names[i];
if (benchSize < name.length) { benchSize = name.length; } if (benchSize < name.length) {
benchSize = name.length;
}
} }
this.nameSize = benchSize + 2; this.nameSize = benchSize + 2;
@@ -146,7 +171,9 @@ BenchWarmer.prototype = {
}, },
startLine: function(name) { startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) { var winners = Benchmark.map(this.winners(this.currentBenches), function(
bench
) {
return bench.name.split(': ')[1]; return bench.name.split(': ')[1];
}); });
@@ -164,7 +191,7 @@ BenchWarmer.prototype = {
if (!bench.error) { if (!bench.error) {
var count = bench.hz, var count = bench.hz,
moe = count * bench.stats.rme / 100, moe = (count * bench.stats.rme) / 100,
minimum, minimum,
maximum; maximum;
@@ -5,7 +5,7 @@ var Handlebars = require('handlebars');
console.log('Testing built Handlebars with Node version ' + process.version); console.log('Testing built Handlebars with Node version ' + process.version);
var template = Handlebars.compile('Author: {{author}}'); var template = Handlebars.compile('Author: {{author}}');
var output = template({author: 'Yehuda'}); var output = template({ author: 'Yehuda' });
assert.strictEqual(output, 'Author: Yehuda'); assert.strictEqual(output, 'Author: Yehuda');
console.log('Success'); console.log('Success');
@@ -1,12 +1,12 @@
import * as Handlebars from 'handlebars/runtime' import * as Handlebars from 'handlebars/runtime';
import hbs from 'handlebars-inline-precompile'; import hbs from 'handlebars-inline-precompile';
import {assertEquals} from "../../webpack-test/src/lib/assert"; import { assertEquals } from '../../webpack-test/src/lib/assert';
Handlebars.registerHelper('loud', function(text) { Handlebars.registerHelper('loud', function(text) {
return text.toUpperCase(); return text.toUpperCase();
}); });
const template = hbs`{{loud name}}`; const template = hbs`{{loud name}}`;
const output = template({name: 'yehuda'}) const output = template({ name: 'yehuda' });
assertEquals(output, 'YEHUDA') assertEquals(output, 'YEHUDA');
@@ -21,7 +21,7 @@ module.exports = {
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { cacheDirectory: false }, options: { cacheDirectory: false }
} }
} }
] ]
@@ -1,6 +1,6 @@
import Handlebars from 'handlebars/dist/handlebars'; import Handlebars from 'handlebars/dist/handlebars';
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda'); assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
@@ -1,6 +1,5 @@
import Handlebars from 'handlebars'; import Handlebars from 'handlebars';
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda'); assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
@@ -1,8 +1,10 @@
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
import testTemplate from './test-template.handlebars'; import testTemplate from './test-template.handlebars';
assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda'); assertEquals(testTemplate({ author: 'Yehuda' }).trim(), 'Author: Yehuda');
const testTemplateRequire = require('./test-template.handlebars'); const testTemplateRequire = require('./test-template.handlebars');
assertEquals(testTemplateRequire({author: 'Yehuda'}).trim(), 'Author: Yehuda'); assertEquals(
testTemplateRequire({ author: 'Yehuda' }).trim(),
'Author: Yehuda'
);
@@ -1,10 +1,10 @@
import * as HandlebarsViaImport from 'handlebars'; import * as HandlebarsViaImport from 'handlebars';
const HandlebarsViaRequire = require('handlebars'); const HandlebarsViaRequire = require('handlebars');
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
HandlebarsViaImport.registerHelper('loud', function(text) { HandlebarsViaImport.registerHelper('loud', function(text) {
return text.toUpperCase(); return text.toUpperCase();
}); });
const template = HandlebarsViaRequire.compile('Author: {{loud author}}'); const template = HandlebarsViaRequire.compile('Author: {{loud author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: YEHUDA'); assertEquals(template({ author: 'Yehuda' }), 'Author: YEHUDA');
@@ -1,6 +1,6 @@
import * as Handlebars from 'handlebars/dist/handlebars'; import * as Handlebars from 'handlebars/dist/handlebars';
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda'); assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
@@ -1,5 +1,5 @@
import * as Handlebars from 'handlebars'; import * as Handlebars from 'handlebars';
import {assertEquals} from './lib/assert'; import { assertEquals } from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda'); assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
@@ -15,8 +15,6 @@ module.exports = {
path: __dirname + '/dist' path: __dirname + '/dist'
}, },
module: { module: {
rules: [ rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }]
{test: /\.handlebars$/, loader: 'handlebars-loader'}
]
} }
}; };
+5 -1
View File
@@ -2,7 +2,11 @@ import runtime from './handlebars.runtime';
// Compiler imports // Compiler imports
import AST from './handlebars/compiler/ast'; import AST from './handlebars/compiler/ast';
import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base'; import {
parser as Parser,
parse,
parseWithoutProcessing
} from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler'; import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler'; import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor'; import Visitor from './handlebars/compiler/visitor';
+13 -7
View File
@@ -1,7 +1,7 @@
import {createFrame, extend, toString} from './utils'; import { createFrame, extend, toString } from './utils';
import Exception from './exception'; import Exception from './exception';
import {registerDefaultHelpers} from './helpers'; import { registerDefaultHelpers } from './helpers';
import {registerDefaultDecorators} from './decorators'; import { registerDefaultDecorators } from './decorators';
import logger from './logger'; import logger from './logger';
export const VERSION = '4.5.3'; export const VERSION = '4.5.3';
@@ -38,7 +38,9 @@ HandlebarsEnvironment.prototype = {
registerHelper: function(name, fn) { registerHelper: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple helpers'); } if (fn) {
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name); extend(this.helpers, name);
} else { } else {
this.helpers[name] = fn; this.helpers[name] = fn;
@@ -53,7 +55,9 @@ HandlebarsEnvironment.prototype = {
extend(this.partials, name); extend(this.partials, name);
} else { } else {
if (typeof partial === 'undefined') { if (typeof partial === 'undefined') {
throw new Exception(`Attempting to register a partial called "${name}" as undefined`); throw new Exception(
`Attempting to register a partial called "${name}" as undefined`
);
} }
this.partials[name] = partial; this.partials[name] = partial;
} }
@@ -64,7 +68,9 @@ HandlebarsEnvironment.prototype = {
registerDecorator: function(name, fn) { registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { throw new Exception('Arg not supported with multiple decorators'); } if (fn) {
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name); extend(this.decorators, name);
} else { } else {
this.decorators[name] = fn; this.decorators[name] = fn;
@@ -77,4 +83,4 @@ HandlebarsEnvironment.prototype = {
export let log = logger.log; export let log = logger.log;
export {createFrame, logger}; export { createFrame, logger };
+10 -6
View File
@@ -5,24 +5,28 @@ let AST = {
// * it is an eligible helper, and // * it is an eligible helper, and
// * it has at least one parameter or hash segment // * it has at least one parameter or hash segment
helperExpression: function(node) { helperExpression: function(node) {
return (node.type === 'SubExpression') return (
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement') node.type === 'SubExpression' ||
&& !!((node.params && node.params.length) || node.hash)); ((node.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
}, },
scopedId: function(path) { scopedId: function(path) {
return (/^\.|this\b/).test(path.original); return /^\.|this\b/.test(path.original);
}, },
// an ID is simple if it only has one part, and that part is not // an ID is simple if it only has one part, and that part is not
// `..` or `this`. // `..` or `this`.
simpleId: function(path) { simpleId: function(path) {
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth; return (
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
);
} }
} }
}; };
// Must be exported as an object rather than the root of the module as the jison lexer // Must be exported as an object rather than the root of the module as the jison lexer
// must modify the object to operate properly. // must modify the object to operate properly.
export default AST; export default AST;
+3 -1
View File
@@ -10,7 +10,9 @@ extend(yy, Helpers);
export function parseWithoutProcessing(input, options) { export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in. // Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { return input; } if (input.type === 'Program') {
return input;
}
parser.yy = yy; parser.yy = yy;
+16 -11
View File
@@ -1,5 +1,5 @@
/* global define */ /* global define */
import {isArray} from '../utils'; import { isArray } from '../utils';
let SourceNode; let SourceNode;
@@ -38,7 +38,7 @@ if (!SourceNode) {
this.src = chunks + this.src; this.src = chunks + this.src;
}, },
toStringWithSourceMap: function() { toStringWithSourceMap: function() {
return {code: this.toString()}; return { code: this.toString() };
}, },
toString: function() { toString: function() {
return this.src; return this.src;
@@ -46,7 +46,6 @@ if (!SourceNode) {
}; };
} }
function castChunk(chunk, codeGen, loc) { function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) { if (isArray(chunk)) {
let ret = []; let ret = [];
@@ -62,7 +61,6 @@ function castChunk(chunk, codeGen, loc) {
return chunk; return chunk;
} }
function CodeGen(srcFile) { function CodeGen(srcFile) {
this.srcFile = srcFile; this.srcFile = srcFile;
this.source = []; this.source = [];
@@ -94,17 +92,22 @@ CodeGen.prototype = {
}, },
empty: function() { empty: function() {
let loc = this.currentLocation || {start: {}}; let loc = this.currentLocation || { start: {} };
return new SourceNode(loc.start.line, loc.start.column, this.srcFile); return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
}, },
wrap: function(chunk, loc = this.currentLocation || {start: {}}) { wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
if (chunk instanceof SourceNode) { if (chunk instanceof SourceNode) {
return chunk; return chunk;
} }
chunk = castChunk(chunk, this, loc); chunk = castChunk(chunk, this, loc);
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk); return new SourceNode(
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
}, },
functionCall: function(fn, type, params) { functionCall: function(fn, type, params) {
@@ -113,13 +116,17 @@ CodeGen.prototype = {
}, },
quotedString: function(str) { quotedString: function(str) {
return '"' + (str + '') return (
'"' +
(str + '')
.replace(/\\/g, '\\\\') .replace(/\\/g, '\\\\')
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
.replace(/\n/g, '\\n') .replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') .replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"'; .replace(/\u2029/g, '\\u2029') +
'"'
);
}, },
objectLiteral: function(obj) { objectLiteral: function(obj) {
@@ -138,7 +145,6 @@ CodeGen.prototype = {
return ret; return ret;
}, },
generateList: function(entries) { generateList: function(entries) {
let ret = this.empty(); let ret = this.empty();
@@ -163,4 +169,3 @@ CodeGen.prototype = {
}; };
export default CodeGen; export default CodeGen;
+74 -28
View File
@@ -1,7 +1,7 @@
/* eslint-disable new-cap */ /* eslint-disable new-cap */
import Exception from '../exception'; import Exception from '../exception';
import {isArray, indexOf, extend} from '../utils'; import { isArray, indexOf, extend } from '../utils';
import AST from './ast'; import AST from './ast';
const slice = [].slice; const slice = [].slice;
@@ -25,7 +25,10 @@ Compiler.prototype = {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i], let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i]; otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false; return false;
} }
} }
@@ -54,16 +57,20 @@ Compiler.prototype = {
options.blockParams = options.blockParams || []; options.blockParams = options.blockParams || [];
options.knownHelpers = extend(Object.create(null), { options.knownHelpers = extend(
'helperMissing': true, Object.create(null),
'blockHelperMissing': true, {
'each': true, helperMissing: true,
'if': true, blockHelperMissing: true,
'unless': true, each: true,
'with': true, if: true,
'log': true, unless: true,
'lookup': true with: true,
}, options.knownHelpers); log: true,
lookup: true
},
options.knownHelpers
);
return this.accept(program); return this.accept(program);
}, },
@@ -165,12 +172,15 @@ Compiler.prototype = {
let params = partial.params; let params = partial.params;
if (params.length > 1) { if (params.length > 1) {
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial); throw new Exception(
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) { } else if (!params.length) {
if (this.options.explicitPartialContext) { if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined'); this.opcode('pushLiteral', 'undefined');
} else { } else {
params.push({type: 'PathExpression', parts: [], depth: 0}); params.push({ type: 'PathExpression', parts: [], depth: 0 });
} }
} }
@@ -208,7 +218,6 @@ Compiler.prototype = {
this.DecoratorBlock(decorator); this.DecoratorBlock(decorator);
}, },
ContentStatement: function(content) { ContentStatement: function(content) {
if (content.value) { if (content.value) {
this.opcode('appendContent', content.value); this.opcode('appendContent', content.value);
@@ -260,13 +269,21 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) { if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name); this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) { } else if (this.options.knownHelpersOnly) {
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr); throw new Exception(
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else { } else {
path.strict = true; path.strict = true;
path.falsy = true; path.falsy = true;
this.accept(path); this.accept(path);
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path)); this.opcode(
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
} }
}, },
@@ -287,7 +304,13 @@ Compiler.prototype = {
this.options.data = true; this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict); this.opcode('lookupData', path.depth, path.parts, path.strict);
} else { } else {
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped); this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
} }
}, },
@@ -329,7 +352,11 @@ Compiler.prototype = {
// HELPERS // HELPERS
opcode: function(name) { opcode: function(name) {
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc }); this.opcodes.push({
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc
});
}, },
addDepth: function(depth) { addDepth: function(depth) {
@@ -386,9 +413,7 @@ Compiler.prototype = {
if (this.stringParams) { if (this.stringParams) {
if (value.replace) { if (value.replace) {
value = value value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
.replace(/^(\.?\.\/)*/g, '')
.replace(/\//g, '.');
} }
if (val.depth) { if (val.depth) {
@@ -444,7 +469,11 @@ Compiler.prototype = {
}, },
blockParamIndex: function(name) { blockParamIndex: function(name) {
for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) { for (
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth], let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name); param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) { if (blockParams && param >= 0) {
@@ -455,8 +484,14 @@ Compiler.prototype = {
}; };
export function precompile(input, options, env) { export function precompile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) { if (
throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input); input == null ||
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
input
);
} }
options = options || {}; options = options || {};
@@ -473,8 +508,14 @@ export function precompile(input, options, env) {
} }
export function compile(input, options = {}, env) { export function compile(input, options = {}, env) {
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) { if (
throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input); input == null ||
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
input
);
} }
options = extend({}, options); options = extend({}, options);
@@ -490,7 +531,12 @@ export function compile(input, options = {}, env) {
function compileInput() { function compileInput() {
let ast = env.parse(input, options), let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options), environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); templateSpec = new env.JavaScriptCompiler().compile(
environment,
options,
undefined,
true
);
return env.template(templateSpec); return env.template(templateSpec);
} }
+22 -13
View File
@@ -4,9 +4,12 @@ function validateClose(open, close) {
close = close.path ? close.path.original : close; close = close.path ? close.path.original : close;
if (open.path.original !== close) { if (open.path.original !== close) {
let errorNode = {loc: open.path.loc}; let errorNode = { loc: open.path.loc };
throw new Exception(open.path.original + " doesn't match " + close, errorNode); throw new Exception(
open.path.original + " doesn't match " + close,
errorNode
);
} }
} }
@@ -38,8 +41,7 @@ export function stripFlags(open, close) {
} }
export function stripComment(comment) { export function stripComment(comment) {
return comment.replace(/^\{\{~?!-?-?/, '') return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
.replace(/-?-?~?\}\}$/, '');
} }
export function preparePath(data, parts, loc) { export function preparePath(data, parts, loc) {
@@ -58,7 +60,7 @@ export function preparePath(data, parts, loc) {
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
if (dig.length > 0) { if (dig.length > 0) {
throw new Exception('Invalid path: ' + original, {loc}); throw new Exception('Invalid path: ' + original, { loc });
} else if (part === '..') { } else if (part === '..') {
depth++; depth++;
} }
@@ -82,7 +84,7 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
let escapeFlag = open.charAt(3) || open.charAt(2), let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&'; escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = (/\*/.test(open)); let decorator = /\*/.test(open);
return { return {
type: decorator ? 'Decorator' : 'MustacheStatement', type: decorator ? 'Decorator' : 'MustacheStatement',
path, path,
@@ -118,21 +120,30 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
}; };
} }
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { export function prepareBlock(
openBlock,
program,
inverseAndProgram,
close,
inverted,
locInfo
) {
if (close && close.path) { if (close && close.path) {
validateClose(openBlock, close); validateClose(openBlock, close);
} }
let decorator = (/\*/.test(openBlock.open)); let decorator = /\*/.test(openBlock.open);
program.blockParams = openBlock.blockParams; program.blockParams = openBlock.blockParams;
let inverse, let inverse, inverseStrip;
inverseStrip;
if (inverseAndProgram) { if (inverseAndProgram) {
if (decorator) { if (decorator) {
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram); throw new Exception(
'Unexpected inverse block on decorator',
inverseAndProgram
);
} }
if (inverseAndProgram.chain) { if (inverseAndProgram.chain) {
@@ -192,7 +203,6 @@ export function prepareProgram(statements, loc) {
}; };
} }
export function preparePartialBlock(open, program, close, locInfo) { export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close); validateClose(open, close);
@@ -207,4 +217,3 @@ export function preparePartialBlock(open, program, close, locInfo) {
loc: this.locInfo(locInfo) loc: this.locInfo(locInfo)
}; };
} }
+173 -54
View File
@@ -1,8 +1,8 @@
import { COMPILER_REVISION, REVISION_CHANGES } from '../base'; import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
import Exception from '../exception'; import Exception from '../exception';
import {isArray} from '../utils'; import { isArray } from '../utils';
import CodeGen from './code-gen'; import CodeGen from './code-gen';
import {dangerousPropertyRegex} from '../helpers/lookup'; import { dangerousPropertyRegex } from '../helpers/lookup';
function Literal(value) { function Literal(value) {
this.value = value; this.value = value;
@@ -13,9 +13,16 @@ function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = { JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide // PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics // alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name/* , type*/) { nameLookup: function(parent, name /* , type*/) {
if (dangerousPropertyRegex.test(name)) { if (dangerousPropertyRegex.test(name)) {
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',', JSON.stringify(name), ')']; const isEnumerable = [
this.aliasable('container.propertyIsEnumerable'),
'.call(',
parent,
',',
JSON.stringify(name),
')'
];
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)']; return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
} }
return _actualLookup(); return _actualLookup();
@@ -91,7 +98,11 @@ JavaScriptCompiler.prototype = {
this.compileChildren(environment, options); this.compileChildren(environment, options);
this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat; this.useDepths =
this.useDepths ||
environment.useDepths ||
environment.useDecorators ||
this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams; this.useBlockParams = this.useBlockParams || environment.useBlockParams;
let opcodes = environment.opcodes, let opcodes = environment.opcodes,
@@ -124,9 +135,20 @@ JavaScriptCompiler.prototype = {
this.decorators.push('return fn;'); this.decorators.push('return fn;');
if (asObject) { if (asObject) {
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]); this.decorators = Function.apply(this, [
'fn',
'props',
'container',
'depth0',
'data',
'blockParams',
'depths',
this.decorators.merge()
]);
} else { } else {
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n'); this.decorators.prepend(
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
);
this.decorators.push('}\n'); this.decorators.push('}\n');
this.decorators = this.decorators.merge(); this.decorators = this.decorators.merge();
} }
@@ -146,7 +168,7 @@ JavaScriptCompiler.prototype = {
ret.useDecorators = true; ret.useDecorators = true;
} }
let {programs, decorators} = this.context; let { programs, decorators } = this.context;
for (i = 0, l = programs.length; i < l; i++) { for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) { if (programs[i]) {
ret[i] = programs[i]; ret[i] = programs[i];
@@ -176,11 +198,11 @@ JavaScriptCompiler.prototype = {
if (!asObject) { if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler); ret.compiler = JSON.stringify(ret.compiler);
this.source.currentLocation = {start: {line: 1, column: 0}}; this.source.currentLocation = { start: { line: 1, column: 0 } };
ret = this.objectLiteral(ret); ret = this.objectLiteral(ret);
if (options.srcName) { if (options.srcName) {
ret = ret.toStringWithSourceMap({file: options.destName}); ret = ret.toStringWithSourceMap({ file: options.destName });
ret.map = ret.map && ret.map.toString(); ret.map = ret.map && ret.map.toString();
} else { } else {
ret = ret.toString(); ret = ret.toString();
@@ -221,7 +243,7 @@ JavaScriptCompiler.prototype = {
Object.keys(this.aliases).forEach(alias => { Object.keys(this.aliases).forEach(alias => {
let node = this.aliases[alias]; let node = this.aliases[alias];
if (node.children && node.referenceCount > 1) { if (node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + (++aliasCount) + '=' + alias; varDeclarations += ', alias' + ++aliasCount + '=' + alias;
node.children[0] = 'alias' + aliasCount; node.children[0] = 'alias' + aliasCount;
} }
}); });
@@ -243,18 +265,23 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params); return Function.apply(this, params);
} else { } else {
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); return this.source.wrap([
'function(',
params.join(','),
') {\n ',
source,
'}'
]);
} }
}, },
mergeSource: function(varDeclarations) { mergeSource: function(varDeclarations) {
let isSimple = this.environment.isSimple, let isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer, appendOnly = !this.forceBuffer,
appendFirst, appendFirst,
sourceSeen, sourceSeen,
bufferStart, bufferStart,
bufferEnd; bufferEnd;
this.source.each((line) => { this.source.each(line => {
if (line.appendToBuffer) { if (line.appendToBuffer) {
if (bufferStart) { if (bufferStart) {
line.prepend(' + '); line.prepend(' + ');
@@ -280,7 +307,6 @@ JavaScriptCompiler.prototype = {
} }
}); });
if (appendOnly) { if (appendOnly) {
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return '); bufferStart.prepend('return ');
@@ -289,7 +315,8 @@ JavaScriptCompiler.prototype = {
this.source.push('return "";'); this.source.push('return "";');
} }
} else { } else {
varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer()); varDeclarations +=
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return buffer + '); bufferStart.prepend('return buffer + ');
@@ -300,7 +327,9 @@ JavaScriptCompiler.prototype = {
} }
if (varDeclarations) { if (varDeclarations) {
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')); this.source.prepend(
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
);
} }
return this.source.merge(); return this.source.merge();
@@ -316,7 +345,9 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly // replace it on the stack with the result of properly
// invoking blockHelperMissing. // invoking blockHelperMissing.
blockValue: function(name) { blockValue: function(name) {
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'), let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)]; params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params); this.setupHelperArgs(name, 0, params);
@@ -334,7 +365,9 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value // On stack, after, if lastHelper: value
ambiguousBlockValue: function() { ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec // We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'), let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)]; params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true); this.setupHelperArgs('', 0, params, true);
@@ -344,9 +377,14 @@ JavaScriptCompiler.prototype = {
params.splice(1, 0, current); params.splice(1, 0, current);
this.pushSource([ this.pushSource([
'if (!', this.lastHelper, ') { ', 'if (!',
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), this.lastHelper,
'}']); ') { ',
current,
' = ',
this.source.functionCall(blockHelperMissing, 'call', params),
'}'
]);
}, },
// [appendContent] // [appendContent]
@@ -376,14 +414,24 @@ JavaScriptCompiler.prototype = {
// Otherwise, the empty string is appended // Otherwise, the empty string is appended
append: function() { append: function() {
if (this.isInline()) { if (this.isInline()) {
this.replaceStack((current) => [' != null ? ', current, ' : ""']); this.replaceStack(current => [' != null ? ', current, ' : ""']);
this.pushSource(this.appendToBuffer(this.popStack())); this.pushSource(this.appendToBuffer(this.popStack()));
} else { } else {
let local = this.popStack(); let local = this.popStack();
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']); this.pushSource([
'if (',
local,
' != null) { ',
this.appendToBuffer(local, undefined, true),
' }'
]);
if (this.environment.isSimple) { if (this.environment.isSimple) {
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']); this.pushSource([
'else { ',
this.appendToBuffer("''", undefined, true),
' }'
]);
} }
} }
}, },
@@ -395,8 +443,14 @@ JavaScriptCompiler.prototype = {
// //
// Escape `value` and append it to the buffer // Escape `value` and append it to the buffer
appendEscaped: function() { appendEscaped: function() {
this.pushSource(this.appendToBuffer( this.pushSource(
[this.aliasable('container.escapeExpression'), '(', this.popStack(), ')'])); this.appendToBuffer([
this.aliasable('container.escapeExpression'),
'(',
this.popStack(),
')'
])
);
}, },
// [getContext] // [getContext]
@@ -480,7 +534,7 @@ JavaScriptCompiler.prototype = {
let len = parts.length; let len = parts.length;
for (; i < len; i++) { for (; i < len; i++) {
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
this.replaceStack((current) => { this.replaceStack(current => {
let lookup = this.nameLookup(current, parts[i], type); let lookup = this.nameLookup(current, parts[i], type);
// We want to ensure that zero and false are handled properly if the context (falsy flag) // We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values. // needs to have the special handling for these values.
@@ -503,7 +557,14 @@ JavaScriptCompiler.prototype = {
// If the `value` is a lambda, replace it on the stack by // If the `value` is a lambda, replace it on the stack by
// the return value of the lambda // the return value of the lambda
resolvePossibleLambda: function() { resolvePossibleLambda: function() {
this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']); this.push([
this.aliasable('container.lambda'),
'(',
this.popStack(),
', ',
this.contextName(0),
')'
]);
}, },
// [pushStringParam] // [pushStringParam]
@@ -543,7 +604,7 @@ JavaScriptCompiler.prototype = {
if (this.hash) { if (this.hash) {
this.hashes.push(this.hash); this.hashes.push(this.hash);
} }
this.hash = {values: {}, types: [], contexts: [], ids: []}; this.hash = { values: {}, types: [], contexts: [], ids: [] };
}, },
popHash: function() { popHash: function() {
let hash = this.hash; let hash = this.hash;
@@ -611,7 +672,12 @@ JavaScriptCompiler.prototype = {
this.decorators.push([ this.decorators.push([
'fn = ', 'fn = ',
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), this.decorators.functionCall(foundDecorator, '', [
'fn',
'props',
'container',
options
]),
' || fn;' ' || fn;'
]); ]);
}, },
@@ -631,17 +697,28 @@ JavaScriptCompiler.prototype = {
let possibleFunctionCalls = []; let possibleFunctionCalls = [];
if (isSimple) { // direct call to helper if (isSimple) {
// direct call to helper
possibleFunctionCalls.push(helper.name); possibleFunctionCalls.push(helper.name);
} }
// call a function from the input object // call a function from the input object
possibleFunctionCalls.push(nonHelper); possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) { if (!this.options.strict) {
possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing')); possibleFunctionCalls.push(
this.aliasable('container.hooks.helperMissing')
);
} }
let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')']; let functionLookupCode = [
let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams); '(',
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
')'
];
let functionCall = this.source.functionCall(
functionLookupCode,
'call',
helper.callParams
);
this.push(functionCall); this.push(functionCall);
}, },
@@ -685,7 +762,11 @@ JavaScriptCompiler.prototype = {
this.emptyHash(); this.emptyHash();
let helper = this.setupHelper(0, name, helperCall); let helper = this.setupHelper(0, name, helperCall);
let helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); let helperName = (this.lastHelper = this.nameLookup(
'helpers',
name,
'helper'
));
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) { if (!this.options.strict) {
@@ -697,10 +778,15 @@ JavaScriptCompiler.prototype = {
} }
this.push([ this.push([
'(', lookup, '(',
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),', lookup,
'(typeof helper === ', this.aliasable('"function"'), ' ? ', helper.paramsInit ? ['),(', helper.paramsInit] : [],
this.source.functionCall('helper', 'call', helper.callParams), ' : helper))' '),',
'(typeof helper === ',
this.aliasable('"function"'),
' ? ',
this.source.functionCall('helper', 'call', helper.callParams),
' : helper))'
]); ]);
}, },
@@ -778,8 +864,13 @@ JavaScriptCompiler.prototype = {
pushId: function(type, name, child) { pushId: function(type, name, child) {
if (type === 'BlockParam') { if (type === 'BlockParam') {
this.pushStackLiteral( this.pushStackLiteral(
'blockParams[' + name[0] + '].path[' + name[1] + ']' 'blockParams[' +
+ (child ? ' + ' + JSON.stringify('.' + child) : '')); name[0] +
'].path[' +
name[1] +
']' +
(child ? ' + ' + JSON.stringify('.' + child) : '')
);
} else if (type === 'PathExpression') { } else if (type === 'PathExpression') {
this.pushString(name); this.pushString(name);
} else if (type === 'SubExpression') { } else if (type === 'SubExpression') {
@@ -794,7 +885,9 @@ JavaScriptCompiler.prototype = {
compiler: JavaScriptCompiler, compiler: JavaScriptCompiler,
compileChildren: function(environment, options) { compileChildren: function(environment, options) {
let children = environment.children, child, compiler; let children = environment.children,
child,
compiler;
for (let i = 0, l = children.length; i < l; i++) { for (let i = 0, l = children.length; i < l; i++) {
child = children[i]; child = children[i];
@@ -807,7 +900,12 @@ JavaScriptCompiler.prototype = {
let index = this.context.programs.length; let index = this.context.programs.length;
child.index = index; child.index = index;
child.name = 'program' + index; child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); this.context.programs[index] = compiler.compile(
child,
options,
this.context,
!this.precompile
);
this.context.decorators[index] = compiler.decorators; this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child; this.context.environments[index] = child;
@@ -870,7 +968,11 @@ JavaScriptCompiler.prototype = {
pushSource: function(source) { pushSource: function(source) {
if (this.pendingContent) { if (this.pendingContent) {
this.source.push( this.source.push(
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)); this.appendToBuffer(
this.source.quotedString(this.pendingContent),
this.pendingLocation
)
);
this.pendingContent = undefined; this.pendingContent = undefined;
} }
@@ -920,7 +1022,9 @@ JavaScriptCompiler.prototype = {
incrStack: function() { incrStack: function() {
this.stackSlot++; this.stackSlot++;
if (this.stackSlot > this.stackVars.length) { this.stackVars.push('stack' + this.stackSlot); } if (this.stackSlot > this.stackVars.length) {
this.stackVars.push('stack' + this.stackSlot);
}
return this.topStackName(); return this.topStackName();
}, },
topStackName: function() { topStackName: function() {
@@ -949,7 +1053,7 @@ JavaScriptCompiler.prototype = {
let inline = this.isInline(), let inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop(); item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && (item instanceof Literal)) { if (!wrapped && item instanceof Literal) {
return item.value; return item.value;
} else { } else {
if (!inline) { if (!inline) {
@@ -964,7 +1068,7 @@ JavaScriptCompiler.prototype = {
}, },
topStack: function() { topStack: function() {
let stack = (this.isInline() ? this.inlineStack : this.compileStack), let stack = this.isInline() ? this.inlineStack : this.compileStack,
item = stack[stack.length - 1]; item = stack[stack.length - 1];
/* istanbul ignore if */ /* istanbul ignore if */
@@ -1009,7 +1113,11 @@ JavaScriptCompiler.prototype = {
let params = [], let params = [],
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
let foundHelper = this.nameLookup('helpers', name, 'helper'), let foundHelper = this.nameLookup('helpers', name, 'helper'),
callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`); callContext = this.aliasable(
`${this.contextName(0)} != null ? ${this.contextName(
0
)} : (container.nullContext || {})`
);
return { return {
params: params, params: params,
@@ -1106,7 +1214,6 @@ JavaScriptCompiler.prototype = {
} }
}; };
(function() { (function() {
const reservedWords = ( const reservedWords = (
'break else new var' + 'break else new var' +
@@ -1127,15 +1234,18 @@ JavaScriptCompiler.prototype = {
' null true false' ' null true false'
).split(' '); ).split(' ');
const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {});
for (let i = 0, l = reservedWords.length; i < l; i++) { for (let i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true; compilerWords[reservedWords[i]] = true;
} }
}()); })();
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
return !JavaScriptCompiler.RESERVED_WORDS[name] && (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/).test(name); return (
!JavaScriptCompiler.RESERVED_WORDS[name] &&
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
);
}; };
function strictLookup(requireTerminal, compiler, parts, type) { function strictLookup(requireTerminal, compiler, parts, type) {
@@ -1151,7 +1261,16 @@ function strictLookup(requireTerminal, compiler, parts, type) {
} }
if (requireTerminal) { if (requireTerminal) {
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )']; return [
compiler.aliasable('container.strict'),
'(',
stack,
', ',
compiler.quotedString(parts[i]),
', ',
JSON.stringify(compiler.source.currentLocation),
' )'
];
} else { } else {
return stack; return stack;
} }
+14 -7
View File
@@ -25,7 +25,8 @@ PrintVisitor.prototype.pad = function(string) {
PrintVisitor.prototype.Program = function(program) { PrintVisitor.prototype.Program = function(program) {
let out = '', let out = '',
body = program.body, body = program.body,
i, l; i,
l;
if (program.blockParams) { if (program.blockParams) {
let blockParams = 'BLOCK PARAMS: ['; let blockParams = 'BLOCK PARAMS: [';
@@ -52,11 +53,14 @@ PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}'); return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
}; };
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
PrintVisitor.prototype.DecoratorBlock = function(block) { block
) {
let out = ''; let out = '';
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'); out += this.pad(
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++; this.padding++;
out += this.pad(this.SubExpression(block)); out += this.pad(this.SubExpression(block));
if (block.program) { if (block.program) {
@@ -66,12 +70,16 @@ PrintVisitor.prototype.DecoratorBlock = function(block) {
this.padding--; this.padding--;
} }
if (block.inverse) { if (block.inverse) {
if (block.program) { this.padding++; } if (block.program) {
this.padding++;
}
out += this.pad('{{^}}'); out += this.pad('{{^}}');
this.padding++; this.padding++;
out += this.accept(block.inverse); out += this.accept(block.inverse);
this.padding--; this.padding--;
if (block.program) { this.padding--; } if (block.program) {
this.padding--;
}
} }
this.padding--; this.padding--;
@@ -134,7 +142,6 @@ PrintVisitor.prototype.PathExpression = function(id) {
return (id.data ? '@' : '') + 'PATH:' + path; return (id.data ? '@' : '') + 'PATH:' + path;
}; };
PrintVisitor.prototype.StringLiteral = function(string) { PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"'; return '"' + string.value + '"';
}; };
+8 -1
View File
@@ -15,7 +15,14 @@ Visitor.prototype = {
// Hacky sanity check: This may have a few false positives for type for the helper // Hacky sanity check: This may have a few false positives for type for the helper
// methods but will generally do the right thing without a lot of overhead. // methods but will generally do the right thing without a lot of overhead.
if (value && !Visitor.prototype[value.type]) { if (value && !Visitor.prototype[value.type]) {
throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); throw new Exception(
'Unexpected node type "' +
value.type +
'" found when accepting ' +
name +
' on ' +
node.type
);
} }
node[name] = value; node[name] = value;
} }
+38 -20
View File
@@ -22,10 +22,10 @@ WhitespaceControl.prototype.Program = function(program) {
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
_isNextWhitespace = isNextWhitespace(body, i, isRoot), _isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace, openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace, closeStandalone = strip.closeStandalone && _isNextWhitespace,
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; inlineStandalone =
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
if (strip.close) { if (strip.close) {
omitRight(body, i, true); omitRight(body, i, true);
@@ -41,7 +41,7 @@ WhitespaceControl.prototype.Program = function(program) {
// If we are on a standalone node, save the indent info for partials // If we are on a standalone node, save the indent info for partials
if (current.type === 'PartialStatement') { if (current.type === 'PartialStatement') {
// Pull out the whitespace from the final line // Pull out the whitespace from the final line
current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1]; current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
} }
} }
} }
@@ -62,9 +62,9 @@ WhitespaceControl.prototype.Program = function(program) {
return program; return program;
}; };
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
WhitespaceControl.prototype.DecoratorBlock = block
WhitespaceControl.prototype.PartialBlockStatement = function(block) { ) {
this.accept(block.program); this.accept(block.program);
this.accept(block.inverse); this.accept(block.inverse);
@@ -112,9 +112,11 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
} }
// Find standalone else statments // Find standalone else statments
if (!this.options.ignoreStandalone if (
&& isPrevWhitespace(program.body) !this.options.ignoreStandalone &&
&& isNextWhitespace(firstInverse.body)) { isPrevWhitespace(program.body) &&
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body); omitLeft(program.body);
omitRight(firstInverse.body); omitRight(firstInverse.body);
} }
@@ -125,13 +127,15 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
return strip; return strip;
}; };
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
WhitespaceControl.prototype.MustacheStatement = function(mustache) { mustache
) {
return mustache.strip; return mustache.strip;
}; };
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
WhitespaceControl.prototype.CommentStatement = function(node) { node
) {
/* istanbul ignore next */ /* istanbul ignore next */
let strip = node.strip || {}; let strip = node.strip || {};
return { return {
@@ -141,7 +145,6 @@ WhitespaceControl.prototype.PartialStatement =
}; };
}; };
function isPrevWhitespace(body, i, isRoot) { function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) { if (i === undefined) {
i = body.length; i = body.length;
@@ -156,7 +159,9 @@ function isPrevWhitespace(body, i, isRoot) {
} }
if (prev.type === 'ContentStatement') { if (prev.type === 'ContentStatement') {
return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original); return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
prev.original
);
} }
} }
function isNextWhitespace(body, i, isRoot) { function isNextWhitespace(body, i, isRoot) {
@@ -171,7 +176,9 @@ function isNextWhitespace(body, i, isRoot) {
} }
if (next.type === 'ContentStatement') { if (next.type === 'ContentStatement') {
return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original); return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
next.original
);
} }
} }
@@ -184,12 +191,19 @@ function isNextWhitespace(body, i, isRoot) {
// content is met. // content is met.
function omitRight(body, i, multiple) { function omitRight(body, i, multiple) {
let current = body[i == null ? 0 : i + 1]; let current = body[i == null ? 0 : i + 1];
if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) { if (
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.rightStripped)
) {
return; return;
} }
let original = current.value; let original = current.value;
current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), ''); current.value = current.value.replace(
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
''
);
current.rightStripped = current.value !== original; current.rightStripped = current.value !== original;
} }
@@ -202,13 +216,17 @@ function omitRight(body, i, multiple) {
// content is met. // content is met.
function omitLeft(body, i, multiple) { function omitLeft(body, i, multiple) {
let current = body[i == null ? body.length - 1 : i - 1]; let current = body[i == null ? body.length - 1 : i - 1];
if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) { if (
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.leftStripped)
) {
return; return;
} }
// We omit the last node if it's whitespace only and not preceded by a non-content node. // We omit the last node if it's whitespace only and not preceded by a non-content node.
let original = current.value; let original = current.value;
current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), ''); current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
current.leftStripped = current.value !== original; current.leftStripped = current.value !== original;
return current.leftStripped; return current.leftStripped;
} }
-1
View File
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) { export function registerDefaultDecorators(instance) {
registerInline(instance); registerInline(instance);
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import {extend} from '../utils'; import { extend } from '../utils';
export default function(instance) { export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) { instance.registerDecorator('inline', function(fn, props, container, options) {
+10 -2
View File
@@ -1,5 +1,13 @@
const errorProps = [
const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack']; 'description',
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];
function Exception(message, node) { function Exception(message, node) {
let loc = node && node.loc, let loc = node && node.loc,
@@ -1,4 +1,4 @@
import {appendContextPath, createFrame, isArray} from '../utils'; import { appendContextPath, createFrame, isArray } from '../utils';
export default function(instance) { export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) { instance.registerHelper('blockHelperMissing', function(context, options) {
@@ -22,8 +22,11 @@ export default function(instance) {
} else { } else {
if (options.data && options.ids) { if (options.data && options.ids) {
let data = createFrame(options.data); let data = createFrame(options.data);
data.contextPath = appendContextPath(options.data.contextPath, options.name); data.contextPath = appendContextPath(
options = {data: data}; options.data.contextPath,
options.name
);
options = { data: data };
} }
return fn(context, options); return fn(context, options);
+19 -5
View File
@@ -1,4 +1,10 @@
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils'; import {
appendContextPath,
blockParams,
createFrame,
isArray,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
@@ -15,10 +21,13 @@ export default function(instance) {
contextPath; contextPath;
if (options.data && options.ids) { if (options.data && options.ids) {
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.'; contextPath =
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
} }
if (isFunction(context)) { context = context.call(this); } if (isFunction(context)) {
context = context.call(this);
}
if (options.data) { if (options.data) {
data = createFrame(options.data); data = createFrame(options.data);
@@ -36,9 +45,14 @@ export default function(instance) {
} }
} }
ret = ret + fn(context[field], { ret =
ret +
fn(context[field], {
data: data, data: data,
blockParams: blockParams([context[field], field], [contextPath + field, null]) blockParams: blockParams(
[context[field], field],
[contextPath + field, null]
)
}); });
} }
+3 -1
View File
@@ -7,7 +7,9 @@ export default function(instance) {
return undefined; return undefined;
} else { } else {
// Someone is actually trying to call something, blow up. // Someone is actually trying to call something, blow up.
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"'); throw new Exception(
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
} }
}); });
} }
+14 -4
View File
@@ -3,8 +3,12 @@ import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('if', function(conditional, options) { instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');} if (arguments.length != 2) {
if (isFunction(conditional)) { conditional = conditional.call(this); } throw new Exception('#if requires exactly one argument');
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// Default behavior is to render the positive path if the value is truthy and not empty. // Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the // The `includeZero` option may be set to treat the condtional as purely not empty based on the
@@ -17,7 +21,13 @@ export default function(instance) {
}); });
instance.registerHelper('unless', function(conditional, options) { instance.registerHelper('unless', function(conditional, options) {
if (arguments.length != 2) { throw new Exception('#unless requires exactly one argument');} if (arguments.length != 2) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash}); throw new Exception('#unless requires exactly one argument');
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
}); });
} }
+1 -1
View File
@@ -14,6 +14,6 @@ export default function(instance) {
} }
args[0] = level; args[0] = level;
instance.log(... args); instance.log(...args);
}); });
} }
+4 -1
View File
@@ -5,7 +5,10 @@ export default function(instance) {
if (!obj) { if (!obj) {
return obj; return obj;
} }
if (dangerousPropertyRegex.test(String(field)) && !Object.prototype.propertyIsEnumerable.call(obj, field)) { if (
dangerousPropertyRegex.test(String(field)) &&
!Object.prototype.propertyIsEnumerable.call(obj, field)
) {
return undefined; return undefined;
} }
return obj[field]; return obj[field];
+17 -4
View File
@@ -1,10 +1,20 @@
import { appendContextPath, blockParams, createFrame, isEmpty, isFunction } from '../utils'; import {
appendContextPath,
blockParams,
createFrame,
isEmpty,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('with', function(context, options) { instance.registerHelper('with', function(context, options) {
if (arguments.length != 2) { throw new Exception('#with requires exactly one argument');} if (arguments.length != 2) {
if (isFunction(context)) { context = context.call(this); } throw new Exception('#with requires exactly one argument');
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn; let fn = options.fn;
@@ -12,7 +22,10 @@ export default function(instance) {
let data = options.data; let data = options.data;
if (options.data && options.ids) { if (options.data && options.ids) {
data = createFrame(options.data); data = createFrame(options.data);
data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]); data.contextPath = appendContextPath(
options.data.contextPath,
options.ids[0]
);
} }
return fn(context, { return fn(context, {
+7 -3
View File
@@ -1,4 +1,4 @@
import {indexOf} from './utils'; import { indexOf } from './utils';
let logger = { let logger = {
methodMap: ['debug', 'info', 'warn', 'error'], methodMap: ['debug', 'info', 'warn', 'error'],
@@ -22,9 +22,13 @@ let logger = {
log: function(level, ...message) { log: function(level, ...message) {
level = logger.lookupLevel(level); level = logger.lookupLevel(level);
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { if (
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level]; let method = logger.methodMap[level];
if (!console[method]) { // eslint-disable-line no-console // eslint-disable-next-line no-console
if (!console[method]) {
method = 'log'; method = 'log';
} }
console[method](...message); // eslint-disable-line no-console console[method](...message); // eslint-disable-line no-console
-1
View File
@@ -1,4 +1,3 @@
/* global window */
export default function(Handlebars) { export default function(Handlebars) {
/* istanbul ignore next */ /* istanbul ignore next */
let root = typeof global !== 'undefined' ? global : window, let root = typeof global !== 'undefined' ? global : window,
+140 -36
View File
@@ -1,30 +1,47 @@
import * as Utils from './utils'; import * as Utils from './utils';
import Exception from './exception'; import Exception from './exception';
import {COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES} from './base'; import {
import {moveHelperToHooks} from './helpers'; COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES
} from './base';
import { moveHelperToHooks } from './helpers';
export function checkRevision(compilerInfo) { export function checkRevision(compilerInfo) {
const compilerRevision = compilerInfo && compilerInfo[0] || 1, const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
currentRevision = COMPILER_REVISION; currentRevision = COMPILER_REVISION;
if (compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= COMPILER_REVISION) { if (
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return; return;
} }
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) { if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision], const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision]; compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' + throw new Exception(
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); 'Template was precompiled with an older version of Handlebars than the current runtime. ' +
'Please update your precompiler to a newer version (' +
runtimeVersions +
') or downgrade your runtime to an older version (' +
compilerVersions +
').'
);
} else { } else {
// Use the embedded version info since the runtime doesn't know about this revision yet // Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' + throw new Exception(
'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); 'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Please update your runtime to a newer version (' +
compilerInfo[1] +
').'
);
} }
} }
export function template(templateSpec, env) { export function template(templateSpec, env) {
/* istanbul ignore next */ /* istanbul ignore next */
if (!env) { if (!env) {
throw new Exception('No environment passed to template'); throw new Exception('No environment passed to template');
@@ -40,7 +57,8 @@ export function template(templateSpec, env) {
env.VM.checkRevision(templateSpec.compiler); env.VM.checkRevision(templateSpec.compiler);
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
const templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7; const templateWasPrecompiledWithCompilerV7 =
templateSpec.compiler && templateSpec.compiler[0] === 7;
function invokePartialWrapper(partial, context, options) { function invokePartialWrapper(partial, context, options) {
if (options.hash) { if (options.hash) {
@@ -51,12 +69,21 @@ export function template(templateSpec, env) {
} }
partial = env.VM.resolvePartial.call(this, partial, context, options); partial = env.VM.resolvePartial.call(this, partial, context, options);
let optionsWithHooks = Utils.extend({}, options, {hooks: this.hooks}); let optionsWithHooks = Utils.extend({}, options, { hooks: this.hooks });
let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks); let result = env.VM.invokePartial.call(
this,
partial,
context,
optionsWithHooks
);
if (result == null && env.compile) { if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); options.partials[options.name] = env.compile(
partial,
templateSpec.compilerOptions,
env
);
result = options.partials[options.name](context, optionsWithHooks); result = options.partials[options.name](context, optionsWithHooks);
} }
if (result != null) { if (result != null) {
@@ -73,7 +100,11 @@ export function template(templateSpec, env) {
} }
return result; return result;
} else { } else {
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); throw new Exception(
'The partial ' +
options.name +
' could not be compiled when running in runtime-only mode'
);
} }
} }
@@ -81,7 +112,9 @@ export function template(templateSpec, env) {
let container = { let container = {
strict: function(obj, name, loc) { strict: function(obj, name, loc) {
if (!obj || !(name in obj)) { if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, { loc: loc }); throw new Exception('"' + name + '" not defined in ' + obj, {
loc: loc
});
} }
return obj[name]; return obj[name];
}, },
@@ -111,7 +144,15 @@ export function template(templateSpec, env) {
let programWrapper = this.programs[i], let programWrapper = this.programs[i],
fn = this.fn(i); fn = this.fn(i);
if (data || depths || blockParams || declaredBlockParams) { if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); programWrapper = wrapProgram(
this,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
);
} else if (!programWrapper) { } else if (!programWrapper) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn); programWrapper = this.programs[i] = wrapProgram(this, i, fn);
} }
@@ -127,7 +168,7 @@ export function template(templateSpec, env) {
mergeIfNeeded: function(param, common) { mergeIfNeeded: function(param, common) {
let obj = param || common; let obj = param || common;
if (param && common && (param !== common)) { if (param && common && param !== common) {
obj = Utils.extend({}, common, param); obj = Utils.extend({}, common, param);
} }
@@ -151,16 +192,37 @@ export function template(templateSpec, env) {
blockParams = templateSpec.useBlockParams ? [] : undefined; blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) { if (templateSpec.useDepths) {
if (options.depths) { if (options.depths) {
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; depths =
context != options.depths[0]
? [context].concat(options.depths)
: options.depths;
} else { } else {
depths = [context]; depths = [context];
} }
} }
function main(context/*, options*/) { function main(context /*, options*/) {
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); return (
'' +
templateSpec.main(
container,
context,
container.helpers,
container.partials,
data,
blockParams,
depths
)
);
} }
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); main = executeDecorators(
templateSpec.main,
main,
container,
options.depths || [],
data,
blockParams
);
return main(context, options); return main(context, options);
} }
ret.isTop = true; ret.isTop = true;
@@ -171,25 +233,32 @@ export function template(templateSpec, env) {
if (templateSpec.usePartial) { if (templateSpec.usePartial) {
// Use mergeIfNeeded here to prevent compiling global partials multiple times // Use mergeIfNeeded here to prevent compiling global partials multiple times
container.partials = container.mergeIfNeeded(options.partials, env.partials); container.partials = container.mergeIfNeeded(
options.partials,
env.partials
);
} }
if (templateSpec.usePartial || templateSpec.useDecorators) { if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = Utils.extend({}, env.decorators, options.decorators); container.decorators = Utils.extend(
{},
env.decorators,
options.decorators
);
} }
container.hooks = {}; container.hooks = {};
let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7; let keepHelperInHelpers =
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else { } else {
container.helpers = options.helpers; container.helpers = options.helpers;
container.partials = options.partials; container.partials = options.partials;
container.decorators = options.decorators; container.decorators = options.decorators;
container.hooks = options.hooks; container.hooks = options.hooks;
} }
}; };
ret._child = function(i, data, blockParams, depths) { ret._child = function(i, data, blockParams, depths) {
@@ -200,24 +269,47 @@ export function template(templateSpec, env) {
throw new Exception('must pass parent depths'); throw new Exception('must pass parent depths');
} }
return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); return wrapProgram(
container,
i,
templateSpec[i],
data,
0,
blockParams,
depths
);
}; };
return ret; return ret;
} }
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { export function wrapProgram(
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) { function prog(context, options = {}) {
let currentDepths = depths; let currentDepths = depths;
if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { if (
depths &&
context != depths[0] &&
!(context === container.nullContext && depths[0] === null)
) {
currentDepths = [context].concat(depths); currentDepths = [context].concat(depths);
} }
return fn(container, return fn(
container,
context, context,
container.helpers, container.partials, container.helpers,
container.partials,
options.data || data, options.data || data,
blockParams && [options.blockParams].concat(blockParams), blockParams && [options.blockParams].concat(blockParams),
currentDepths); currentDepths
);
} }
prog = executeDecorators(fn, prog, container, depths, data, blockParams); prog = executeDecorators(fn, prog, container, depths, data, blockParams);
@@ -259,8 +351,10 @@ export function invokePartial(partial, context, options) {
options.data = createFrame(options.data); options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure // Wrapper function to get access to currentPartialBlock from the closure
let fn = options.fn; let fn = options.fn;
partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) { partialBlock = options.data['partial-block'] = function partialBlockWrapper(
context,
options = {}
) {
// Restore the partial-block from the closure for the execution of the block // Restore the partial-block from the closure for the execution of the block
// i.e. the part inside the block of the partial call. // i.e. the part inside the block of the partial call.
options.data = createFrame(options.data); options.data = createFrame(options.data);
@@ -283,7 +377,9 @@ export function invokePartial(partial, context, options) {
} }
} }
export function noop() { return ''; } export function noop() {
return '';
}
function initData(context, data) { function initData(context, data) {
if (!data || !('root' in data)) { if (!data || !('root' in data)) {
@@ -296,7 +392,15 @@ function initData(context, data) {
function executeDecorators(fn, prog, container, depths, data, blockParams) { function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) { if (fn.decorator) {
let props = {}; let props = {};
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); prog = fn.decorator(
prog,
props,
container,
depths && depths[0],
data,
blockParams,
depths
);
Utils.extend(prog, props); Utils.extend(prog, props);
} }
return prog; return prog;
+16 -9
View File
@@ -1,4 +1,3 @@
const escape = { const escape = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
@@ -16,7 +15,7 @@ function escapeChar(chr) {
return escape[chr]; return escape[chr];
} }
export function extend(obj/* , ...source */) { export function extend(obj /* , ...source */) {
for (let i = 1; i < arguments.length; i++) { for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) { for (let key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
@@ -40,16 +39,23 @@ let isFunction = function(value) {
/* istanbul ignore next */ /* istanbul ignore next */
if (isFunction(/x/)) { if (isFunction(/x/)) {
isFunction = function(value) { isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]'; return (
typeof value === 'function' &&
toString.call(value) === '[object Function]'
);
}; };
} }
export {isFunction}; export { isFunction };
/* eslint-enable func-style */ /* eslint-enable func-style */
/* istanbul ignore next */ /* istanbul ignore next */
export const isArray = Array.isArray || function(value) { export const isArray =
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; Array.isArray ||
}; function(value) {
return value && typeof value === 'object'
? toString.call(value) === '[object Array]'
: false;
};
// Older IE versions do not directly support indexOf so we must implement our own, sadly. // Older IE versions do not directly support indexOf so we must implement our own, sadly.
export function indexOf(array, value) { export function indexOf(array, value) {
@@ -61,7 +67,6 @@ export function indexOf(array, value) {
return -1; return -1;
} }
export function escapeExpression(string) { export function escapeExpression(string) {
if (typeof string !== 'string') { if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe // don't escape SafeStrings, since they're already safe
@@ -79,7 +84,9 @@ export function escapeExpression(string) {
string = '' + string; string = '' + string;
} }
if (!possible.test(string)) { return string; } if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar); return string.replace(badChars, escapeChar);
} }
+66 -24
View File
@@ -2,9 +2,8 @@
import Async from 'neo-async'; import Async from 'neo-async';
import fs from 'fs'; import fs from 'fs';
import * as Handlebars from './handlebars'; import * as Handlebars from './handlebars';
import {basename} from 'path'; import { basename } from 'path';
import {SourceMapConsumer, SourceNode} from 'source-map'; import { SourceMapConsumer, SourceNode } from 'source-map';
module.exports.loadTemplates = function(opts, callback) { module.exports.loadTemplates = function(opts, callback) {
loadStrings(opts, function(err, strings) { loadStrings(opts, function(err, strings) {
@@ -27,12 +26,17 @@ function loadStrings(opts, callback) {
let strings = arrayCast(opts.string), let strings = arrayCast(opts.string),
names = arrayCast(opts.name); names = arrayCast(opts.name);
if (names.length !== strings.length if (names.length !== strings.length && strings.length > 1) {
&& strings.length > 1) { return callback(
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs')); new Handlebars.Exception(
'Number of names did not match the number of string inputs'
)
);
} }
Async.map(strings, function(string, callback) { Async.map(
strings,
function(string, callback) {
if (string !== '-') { if (string !== '-') {
callback(undefined, string); callback(undefined, string);
} else { } else {
@@ -55,22 +59,32 @@ function loadStrings(opts, callback) {
source: string source: string
})); }));
callback(err, strings); callback(err, strings);
}); }
);
} }
function loadFiles(opts, callback) { function loadFiles(opts, callback) {
// Build file extension pattern // Build file extension pattern
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; }); let extension = (opts.extension || 'handlebars').replace(
/[\\^$*+?.():=!|{}\-[\]]/g,
function(arg) {
return '\\' + arg;
}
);
extension = new RegExp('\\.' + extension + '$'); extension = new RegExp('\\.' + extension + '$');
let ret = [], let ret = [],
queue = (opts.files || []).map((template) => ({template, root: opts.root})); queue = (opts.files || []).map(template => ({ template, root: opts.root }));
Async.whilst(() => queue.length, function(callback) { Async.whilst(
let {template: path, root} = queue.shift(); () => queue.length,
function(callback) {
let { template: path, root } = queue.shift();
fs.stat(path, function(err, stat) { fs.stat(path, function(err, stat) {
if (err) { if (err) {
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`)); return callback(
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
} }
if (stat.isDirectory()) { if (stat.isDirectory()) {
@@ -84,8 +98,11 @@ function loadFiles(opts, callback) {
children.forEach(function(file) { children.forEach(function(file) {
let childPath = path + '/' + file; let childPath = path + '/' + file;
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) { if (
queue.push({template: childPath, root: root || path}); extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({ template: childPath, root: root || path });
} }
}); });
@@ -128,7 +145,8 @@ function loadFiles(opts, callback) {
} else { } else {
callback(undefined, ret); callback(undefined, ret);
} }
}); }
);
} }
module.exports.cli = function(opts) { module.exports.cli = function(opts) {
@@ -138,7 +156,9 @@ module.exports.cli = function(opts) {
} }
if (!opts.templates.length && !opts.hasDirectory) { if (!opts.templates.length && !opts.hasDirectory) {
throw new Handlebars.Exception('Must define at least one template or directory.'); throw new Handlebars.Exception(
'Must define at least one template or directory.'
);
} }
if (opts.simple && opts.min) { if (opts.simple && opts.min) {
@@ -147,12 +167,18 @@ module.exports.cli = function(opts) {
const multiple = opts.templates.length !== 1 || opts.hasDirectory; const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) { if (opts.simple && multiple) {
throw new Handlebars.Exception('Unable to output multiple templates in simple mode'); throw new Handlebars.Exception(
'Unable to output multiple templates in simple mode'
);
} }
// Force simple mode if we have only one template and it's unnamed. // Force simple mode if we have only one template and it's unnamed.
if (!opts.amd && !opts.commonjs && opts.templates.length === 1 if (
&& !opts.templates[0].name) { !opts.amd &&
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true; opts.simple = true;
} }
@@ -172,7 +198,11 @@ module.exports.cli = function(opts) {
let output = new SourceNode(); let output = new SourceNode();
if (!opts.simple) { if (!opts.simple) {
if (opts.amd) { if (opts.amd) {
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'); output.add(
"define(['" +
opts.handlebarPath +
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
);
} else if (opts.commonjs) { } else if (opts.commonjs) {
output.add('var Handlebars = require("' + opts.commonjs + '");'); output.add('var Handlebars = require("' + opts.commonjs + '");');
} else { } else {
@@ -206,7 +236,10 @@ module.exports.cli = function(opts) {
// If we are generating a source map, we have to reconstruct the SourceNode object // If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) { if (opts.map) {
let consumer = new SourceMapConsumer(precompiled.map); let consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer); precompiled = SourceNode.fromStringWithSourceMap(
precompiled.code,
consumer
);
} }
if (opts.simple) { if (opts.simple) {
@@ -219,7 +252,14 @@ module.exports.cli = function(opts) {
if (opts.amd && !multiple) { if (opts.amd && !multiple) {
output.add('return '); output.add('return ');
} }
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']); output.add([
objectName,
"['",
template.name,
"'] = template(",
precompiled,
');\n'
]);
} }
}); });
@@ -286,7 +326,9 @@ function minify(output, sourceMapFile) {
throw e; throw e;
} }
// it does not exist! // it does not exist!
console.error('Code minimization is disabled due to missing uglify-js dependency'); console.error(
'Code minimization is disabled due to missing uglify-js dependency'
);
return output; return output;
} }
return require('uglify-js').minify(output.code, { return require('uglify-js').minify(output.code, {
+124 -44
View File
@@ -7,44 +7,102 @@ describe('ast', function() {
describe('BlockStatement', function() { describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() { it('should throw on mustache mismatch', function() {
shouldThrow(function() { shouldThrow(
function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}'); handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, Handlebars.Exception, "foo doesn't match bar - 2:5"); },
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
}); });
}); });
describe('helpers', function() { describe('helpers', function() {
describe('#helperExpression', function() { describe('#helperExpression', function() {
it('should handle mustache statements', function() { it('should handle mustache statements', function() {
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false); equals(
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true); AST.helpers.helperExpression({
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true); 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() { it('should handle block statements', function() {
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false); equals(
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true); AST.helpers.helperExpression({
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true); 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() { 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() { 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(
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false); AST.helpers.helperExpression({ type: 'PartialStatement' }),
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false); 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: 'StringLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false); equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false); equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false); equals(
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false); 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: 'Hash' }), false);
equals(AST.helpers.helperExpression({type: 'HashPair'}), false); equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
}); });
}); });
}); });
@@ -61,17 +119,18 @@ describe('ast', function() {
/* eslint-disable no-multi-spaces */ /* eslint-disable no-multi-spaces */
ast = Handlebars.parse( ast = Handlebars.parse(
'line 1 {{line1Token}}\n' // 1 'line 1 {{line1Token}}\n' + // 1
+ ' line 2 {{line2token}}\n' // 2 ' line 2 {{line2token}}\n' + // 2
+ ' line 3 {{#blockHelperOnLine3}}\n' // 3 ' line 3 {{#blockHelperOnLine3}}\n' + // 3
+ 'line 4{{line4token}}\n' // 4 'line 4{{line4token}}\n' + // 4
+ 'line5{{else}}\n' // 5 'line5{{else}}\n' + // 5
+ '{{line6Token}}\n' // 6 '{{line6Token}}\n' + // 6
+ '{{/blockHelperOnLine3}}\n' // 7 '{{/blockHelperOnLine3}}\n' + // 7
+ '{{#open}}\n' // 8 '{{#open}}\n' + // 8
+ '{{else inverse}}\n' // 9 '{{else inverse}}\n' + // 9
+ '{{else}}\n' // 10 '{{else}}\n' + // 10
+ '{{/open}}'); // 11 '{{/open}}'
); // 11
/* eslint-enable no-multi-spaces */ /* eslint-enable no-multi-spaces */
body = ast.body; body = ast.body;
@@ -149,7 +208,9 @@ describe('ast', function() {
}); });
it('block statements', 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[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n '); equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
@@ -167,7 +228,9 @@ describe('ast', function() {
}); });
describe('blocks - parseWithoutProcessing', function() { describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', 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]; block = ast.body[1];
equals(ast.body[0].value, ' '); equals(ast.body[0].value, ' ');
@@ -178,13 +241,17 @@ describe('ast', function() {
equals(ast.body[2].value, ' '); equals(ast.body[2].value, ' ');
}); });
it('initial block mustaches', function() { 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]; block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n '); equals(block.program.body[0].value, ' \nfoo\n ');
}); });
it('mustaches with children', function() { 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]; block = ast.body[0];
equals(block.program.body[0].value, ' \n'); equals(block.program.body[0].value, ' \n');
@@ -192,7 +259,9 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n '); equals(block.program.body[2].value, '\n ');
}); });
it('nested block mustaches', function() { 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, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -202,7 +271,9 @@ describe('ast', function() {
equals(block.inverse.body[0].value, ' \n bar \n '); equals(block.inverse.body[0].value, ' \n bar \n ');
}); });
it('column 0 block mustaches', function() { it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '), var ast = Handlebars.parseWithoutProcessing(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].omit, undefined); equals(ast.body[0].omit, undefined);
@@ -215,7 +286,9 @@ describe('ast', function() {
}); });
describe('blocks', function() { describe('blocks', function() {
it('marks block mustaches as standalone', function() { it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '), var ast = Handlebars.parse(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].value, ''); equals(ast.body[0].value, '');
@@ -240,7 +313,9 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n'); equals(block.program.body[2].value, '\n');
}); });
it('marks nested block mustaches as standalone', function() { it('marks nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'), var ast = Handlebars.parse(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -252,7 +327,9 @@ describe('ast', function() {
equals(body[0].value, ''); equals(body[0].value, '');
}); });
it('does not mark nested block mustaches as standalone', function() { it('does not mark nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'), var ast = Handlebars.parse(
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -264,7 +341,9 @@ describe('ast', function() {
equals(body[0].omit, undefined); equals(body[0].omit, undefined);
}); });
it('does not mark nested initial block mustaches as standalone', function() { 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}}'), var ast = Handlebars.parse(
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[0]; block = body[0];
@@ -275,7 +354,9 @@ describe('ast', function() {
}); });
it('marks column 0 block mustaches as standalone', function() { it('marks column 0 block mustaches as standalone', function() {
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '), var ast = Handlebars.parse(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].omit, undefined); equals(ast.body[0].omit, undefined);
@@ -348,4 +429,3 @@ describe('ast', function() {
}); });
}); });
}); });
+424 -121
View File
@@ -18,8 +18,12 @@ describe('basic context', function() {
}); });
it('compiling with a basic context', function() { it('compiling with a basic context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!', shouldCompileTo(
'It works if all the required keys are provided'); '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() { it('compiling with a string context', function() {
@@ -27,15 +31,26 @@ describe('basic context', function() {
}); });
it('compiling with an undefined 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() { it('comments', function() {
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!', shouldCompileTo(
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!', '{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
'comments are ignored'); { cruel: 'cruel', world: 'world' },
'Goodbye\ncruel\nworld!',
'comments are ignored'
);
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah'); shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah'); shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
@@ -47,52 +62,91 @@ describe('basic context', function() {
it('boolean', function() { it('boolean', function() {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!', shouldCompileTo(
'booleans show the contents when true'); string,
{ goodbye: true, world: 'world' },
'GOODBYE cruel world!',
'booleans show the contents when true'
);
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!', shouldCompileTo(
'booleans do not show the contents when false'); string,
{ goodbye: false, world: 'world' },
'cruel world!',
'booleans do not show the contents when false'
);
}); });
it('zeros', function() { it('zeros', function() {
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0}, shouldCompileTo(
'num1: 42, num2: 0'); 'num1: {{num1}}, num2: {{num2}}',
{ num1: 42, num2: 0 },
'num1: 42, num2: 0'
);
shouldCompileTo('num: {{.}}', 0, 'num: 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() { it('false', function() {
/* eslint-disable no-new-wrappers */ /* 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: {{.}}', 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(
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false'); '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 */ /* eslint-enable */
}); });
it('should handle undefined and null', function() { it('should handle undefined and null', function() {
shouldCompileTo('{{awesome undefined null}}', shouldCompileTo(
'{{awesome undefined null}}',
{ {
awesome: function(_undefined, _null, options) { awesome: function(_undefined, _null, options) {
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options); return (
(_undefined === undefined) +
' ' +
(_null === null) +
' ' +
typeof options
);
} }
}, },
'true true object'); 'true true object'
shouldCompileTo('{{undefined}}', );
shouldCompileTo(
'{{undefined}}',
{ {
'undefined': function() { undefined: function() {
return 'undefined!'; return 'undefined!';
} }
}, },
'undefined!'); 'undefined!'
shouldCompileTo('{{null}}', );
shouldCompileTo(
'{{null}}',
{ {
'null': function() { null: function() {
return 'null!'; return 'null!';
} }
}, },
'null!'); 'null!'
);
}); });
it('newlines', function() { it('newlines', function() {
@@ -101,183 +155,413 @@ describe('basic context', function() {
}); });
it('escaping text', function() { it('escaping text', function() {
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes"); shouldCompileTo(
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored"); "Awesome's",
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"); "Awesome's",
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript'); "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() { it('escaping expressions', function() {
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>', shouldCompileTo(
"expressions with 3 handlebars aren't escaped"); '{{{awesome}}}',
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with 3 handlebars aren't escaped"
);
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>', shouldCompileTo(
"expressions with {{& handlebars aren't escaped"); '{{&awesome}}',
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with {{& handlebars aren't escaped"
);
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;', shouldCompileTo(
'by default expressions should be escaped'); '{{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;', shouldCompileTo(
'escaping should properly handle amperstands'); '{{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() { it("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }}; var hash = {
shouldCompileTo('{{awesome}}', hash, '&\'\\<>', awesome: function() {
"functions returning safestrings aren't escaped"); return new Handlebars.SafeString("&'\\<>");
}
};
shouldCompileTo(
'{{awesome}}',
hash,
"&'\\<>",
"functions returning safestrings aren't escaped"
);
}); });
it('functions', function() { it('functions', function() {
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome', shouldCompileTo(
'functions are called and render their output'); '{{awesome}}',
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome', {
'functions are bound to the context'); 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() { it('functions with context argument', function() {
shouldCompileTo('{{awesome frank}}', shouldCompileTo(
{awesome: function(context) { return context; }, '{{awesome frank}}',
frank: 'Frank'}, {
'Frank', 'functions are called with context arguments'); awesome: function(context) {
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('pathed functions with context argument', function() { it('pathed functions with context argument', function() {
shouldCompileTo('{{bar.awesome frank}}', shouldCompileTo(
{bar: {awesome: function(context) { return context; }}, '{{bar.awesome frank}}',
frank: 'Frank'}, {
'Frank', 'functions are called with context arguments'); bar: {
awesome: function(context) {
return context;
}
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('depthed functions with context argument', function() { it('depthed functions with context argument', function() {
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}', shouldCompileTo(
{awesome: function(context) { return context; }, '{{#with frank}}{{../awesome .}}{{/with}}',
frank: 'Frank'}, {
'Frank', 'functions are called with context arguments'); awesome: function(context) {
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('block functions with context argument', function() { it('block functions with context argument', function() {
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}', shouldCompileTo(
{awesome: function(context, options) { return options.fn(context); }}, '{{#awesome 1}}inner {{.}}{{/awesome}}',
'inner 1', 'block functions are called with context and options'); {
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() { it('depthed block functions with context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}', shouldCompileTo(
{value: true, awesome: function(context, options) { return options.fn(context); }}, '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
'inner 1', 'block functions are called with context and options'); {
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() { it('block functions without context argument', function() {
shouldCompileTo('{{#awesome}}inner{{/awesome}}', shouldCompileTo(
{awesome: function(options) { return options.fn(this); }}, '{{#awesome}}inner{{/awesome}}',
'inner', 'block functions are called with options'); {
awesome: function(options) {
return options.fn(this);
}
},
'inner',
'block functions are called with options'
);
}); });
it('pathed block functions without context argument', function() { it('pathed block functions without context argument', function() {
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}', shouldCompileTo(
{foo: {awesome: function() { return this; }}}, '{{#foo.awesome}}inner{{/foo.awesome}}',
'inner', 'block functions are called with options'); {
foo: {
awesome: function() {
return this;
}
}
},
'inner',
'block functions are called with options'
);
}); });
it('depthed block functions without context argument', function() { it('depthed block functions without context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}', shouldCompileTo(
{value: true, awesome: function() { return this; }}, '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
'inner', 'block functions are called with options'); {
value: true,
awesome: function() {
return this;
}
},
'inner',
'block functions are called with options'
);
}); });
it('paths with hyphens', function() { it('paths with hyphens', function() {
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)'); shouldCompileTo(
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)'); '{{foo-bar}}',
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)'); { '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() { it('nested paths', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}}, shouldCompileTo(
'Goodbye beautiful world!', 'Nested paths access nested objects'); 'Goodbye {{alan/expression}} world!',
{ alan: { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Nested paths access nested objects'
);
}); });
it('nested paths with empty string value', function() { it('nested paths with empty string value', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}}, shouldCompileTo(
'Goodbye world!', 'Nested paths access nested objects with empty string'); 'Goodbye {{alan/expression}} world!',
{ alan: { expression: '' } },
'Goodbye world!',
'Nested paths access nested objects with empty string'
);
}); });
it('literal paths', function() { it('literal paths', function() {
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}}, shouldCompileTo(
'Goodbye beautiful world!', 'Literal paths can be used'); 'Goodbye {{[@alan]/expression}} world!',
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}}, { '@alan': { expression: 'beautiful' } },
'Goodbye beautiful world!', 'Literal paths can be used'); '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() { it('literal references', function() {
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!'); shouldCompileTo(
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!'); 'Goodbye {{[foo bar]}} world!',
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!'); { 'foo bar': 'beautiful' },
shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!'); '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() { 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() { it('complex but empty paths', function() {
shouldCompileTo('{{person/name}}', {person: {name: null}}, ''); shouldCompileTo('{{person/name}}', { person: { name: null } }, '');
shouldCompileTo('{{person/name}}', {person: {}}, ''); shouldCompileTo('{{person/name}}', { person: {} }, '');
}); });
it('this keyword in paths', function() { it('this keyword in paths', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}'; var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']}; var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE', shouldCompileTo(
'This keyword in paths evaluates to current context'); string,
hash,
'goodbyeGoodbyeGOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{this/text}}{{/hellos}}'; string = '{{#hellos}}{{this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]}; hash = {
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths'); 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() { it('this keyword nested inside path', function() {
shouldThrow(function() { shouldThrow(
function() {
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}'); CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
}, Error, 'Invalid path: text/this - 1:13'); },
Error,
'Invalid path: text/this - 1:13'
);
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar'); shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar');
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar'); shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar');
}); });
it('this keyword in helpers', function() { it('this keyword in helpers', function() {
var helpers = {foo: function(value) { var helpers = {
foo: function(value) {
return 'bar ' + value; return 'bar ' + value;
}}; }
};
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}'; var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']}; var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE', shouldCompileTo(
'This keyword in paths evaluates to current context'); string,
[hash, helpers],
'bar goodbyebar Goodbyebar GOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{foo this/text}}{{/hellos}}'; string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]}; hash = {
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths'); 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() { it('this keyword nested inside helpers param', function() {
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
shouldThrow(function() { shouldThrow(
function() {
CompilerContext.compile(string); CompilerContext.compile(string);
}, Error, 'Invalid path: text/this - 1:17'); },
Error,
'Invalid path: text/this - 1:17'
);
shouldCompileTo( shouldCompileTo(
'{{foo [this]}}', '{{foo [this]}}',
{foo: function(value) { return value; }, 'this': 'bar'}, {
'bar'); foo: function(value) {
return value;
},
this: 'bar'
},
'bar'
);
shouldCompileTo( shouldCompileTo(
'{{foo text/[this]}}', '{{foo text/[this]}}',
{foo: function(value) { return value; }, text: {'this': 'bar'}}, {
'bar'); foo: function(value) {
return value;
},
text: { this: 'bar' }
},
'bar'
);
}); });
it('pass string literals', function() { it('pass string literals', function() {
shouldCompileTo('{{"foo"}}', {}, ''); shouldCompileTo('{{"foo"}}', {}, '');
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar'); shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz'); shouldCompileTo(
'{{#"foo"}}{{.}}{{/"foo"}}',
{ foo: ['bar', 'baz'] },
'barbaz'
);
}); });
it('pass number literals', function() { it('pass number literals', function() {
@@ -285,13 +569,21 @@ describe('basic context', function() {
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar'); shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
shouldCompileTo('{{12.34}}', {}, ''); shouldCompileTo('{{12.34}}', {}, '');
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar'); 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() { it('pass boolean literals', function() {
shouldCompileTo('{{true}}', {}, ''); shouldCompileTo('{{true}}', {}, '');
shouldCompileTo('{{true}}', { '': 'foo' }, ''); shouldCompileTo('{{true}}', { '': 'foo' }, '');
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo'); shouldCompileTo('{{false}}', { false: 'foo' }, 'foo');
}); });
it('should handle literals in subexpression', function() { it('should handle literals in subexpression', function() {
@@ -300,6 +592,17 @@ describe('basic context', function() {
return arg; return arg;
} }
}; };
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar'); shouldCompileTo(
'{{foo (false)}}',
[
{
false: function() {
return 'bar';
}
},
helpers
],
'bar'
);
}); });
}); });
+247 -90
View File
@@ -1,51 +1,99 @@
describe('blocks', function() { describe('blocks', function() {
it('array', function() { it('array', function() {
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; var hash = {
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!', goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
'Arrays iterate over the contents when not empty'); 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!', shouldCompileTo(
'Arrays ignore the contents when empty'); string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
}); });
it('array without data', function() { it('array without data', function() {
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'); var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , false],
'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
);
}); });
it('array with @index', function() { it('array with @index', function() {
var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); 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() { it('empty block', function() {
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; var hash = {
shouldCompileTo(string, hash, 'cruel world!', goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
'Arrays iterate over the contents when not empty'); world: 'world'
};
shouldCompileTo(
string,
hash,
'cruel world!',
'Arrays iterate over the contents when not empty'
);
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!', shouldCompileTo(
'Arrays ignore the contents when empty'); string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
}); });
it('block with complex lookup', function() { it('block with complex lookup', function() {
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}'; 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! ', shouldCompileTo(
'Templates can access variables in contexts up the stack with relative path syntax'); 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() { it('multiple blocks with complex lookup', function() {
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}'; 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'); shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
}); });
@@ -59,111 +107,201 @@ describe('blocks', function() {
}); });
it('block with deep nested complex lookup', function() { it('block with deep nested complex lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'; var string =
var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] }; '{{#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!'); shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
}); });
it('works with cached blocks', function() { 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'); equals(result, 'Alan JohnsonAlan Johnson');
}); });
describe('inverted sections', function() { describe('inverted sections', function() {
it('inverted sections with unset value', 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 = {}; 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() { it('inverted section with false value', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'; var string =
var hash = {goodbyes: false}; '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.'); var hash = { goodbyes: false };
shouldCompileTo(
string,
hash,
'Right On!',
'Inverted section rendered when value is false.'
);
}); });
it('inverted section with empty set', function() { it('inverted section with empty set', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'; var string =
var hash = {goodbyes: []}; '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.'); var hash = { goodbyes: [] };
shouldCompileTo(
string,
hash,
'Right On!',
'Inverted section rendered when value is empty set.'
);
}); });
it('block inverted sections', function() { it('block inverted sections', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'}, shouldCompileTo(
'No people'); '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people' },
'No people'
);
}); });
it('chained inverted sections', function() { it('chained inverted sections', function() {
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'}, shouldCompileTo(
'No people'); '{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'}, { none: 'No people' },
'No people'); 'No people'
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/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() { it('chained inverted sections with mismatch', function() {
shouldThrow(function() { shouldThrow(function() {
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'}, shouldCompileTo(
'No people'); '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
{ none: 'No people' },
'No people'
);
}, Error); }, Error);
}); });
it('block inverted sections with empty arrays', function() { it('block inverted sections with empty arrays', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []}, shouldCompileTo(
'No people'); '{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people', people: [] },
'No people'
);
}); });
}); });
describe('standalone sections', function() { describe('standalone sections', function() {
it('block standalone else sections', function() { it('block standalone else sections', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'}, shouldCompileTo(
'No people\n'); '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'}, { none: 'No people' },
'No people\n'); '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() { it('block standalone else sections can be disabled', function() {
shouldCompileTo( shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}], [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
'\nNo people\n\n'); '\nNo people\n\n'
);
shouldCompileTo( shouldCompileTo(
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n', '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}], [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
'\nNo people\n\n'); '\nNo people\n\n'
);
}); });
it('block standalone chained else sections', function() { it('block standalone chained else sections', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'}, shouldCompileTo(
'No people\n'); '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'}, { none: 'No people' },
'No people\n'); '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() { 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() { describe('compat mode', function() {
it('block with deep recursive lookup lookup', function() { it('block with deep recursive lookup lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'; var string =
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }; '{{#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() { it('block with deep recursive pathed lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; var string =
var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] }; '{{#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() { it('block with missed recursive lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; var string =
var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] }; '{{#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 '
);
}); });
}); });
@@ -182,8 +320,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator}}{{/helper}}', '{{#helper}}{{*decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, { hash: {}, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should apply allow undefined return', function() { it('should apply allow undefined return', function() {
var helpers = { var helpers = {
@@ -198,8 +337,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator}}suc{{/helper}}', '{{#helper}}{{*decorator}}suc{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, { hash: {}, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should apply block decorators', function() { it('should apply block decorators', function() {
@@ -216,8 +356,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, { hash: {}, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should support nested decorators', function() { it('should support nested decorators', function() {
var helpers = { var helpers = {
@@ -236,8 +377,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, { hash: {}, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should apply multiple decorators', function() { it('should apply multiple decorators', function() {
@@ -254,8 +396,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, { hash: {}, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should access parent variables', function() { it('should access parent variables', function() {
@@ -272,8 +415,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator foo}}{{/helper}}', '{{#helper}}{{*decorator foo}}{{/helper}}',
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators}, { hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
'success'); 'success'
);
}); });
it('should work with root program', function() { it('should work with root program', function() {
var run; var run;
@@ -286,8 +430,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{*decorator "success"}}', '{{*decorator "success"}}',
{hash: {'foo': 'success'}, decorators: decorators}, { hash: { foo: 'success' }, decorators: decorators },
''); ''
);
equals(run, true); equals(run, true);
}); });
it('should fail when accessing variables from root', function() { it('should fail when accessing variables from root', function() {
@@ -301,8 +446,9 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{*decorator foo}}', '{{*decorator foo}}',
{hash: {'foo': 'fail'}, decorators: decorators}, { hash: { foo: 'fail' }, decorators: decorators },
''); ''
);
equals(run, true); equals(run, true);
}); });
@@ -335,12 +481,23 @@ describe('blocks', function() {
equals(handlebarsEnv.decorators.bar, undefined); equals(handlebarsEnv.decorators.bar, undefined);
}); });
it('fails with multiple and args', function() { it('fails with multiple and args', function() {
shouldThrow(function() { shouldThrow(
handlebarsEnv.registerDecorator({ function() {
world: function() { return 'world!'; }, handlebarsEnv.registerDecorator(
testHelper: function() { return 'found it!'; } {
}, {}); world: function() {
}, Error, 'Arg not supported with multiple decorators'); return 'world!';
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
}); });
}); });
}); });
+370 -107
View File
@@ -2,64 +2,156 @@ describe('builtin helpers', function() {
describe('#if', function() { describe('#if', function() {
it('if', function() { it('if', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!', shouldCompileTo(
'if with boolean argument shows the contents when true'); string,
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!', { goodbye: true, world: 'world' },
'if with string argument shows the contents'); 'GOODBYE cruel world!',
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!', 'if with boolean argument shows the contents when true'
'if with boolean argument does not show the contents when false'); );
shouldCompileTo(string, {world: 'world'}, 'cruel world!', shouldCompileTo(
'if with undefined does not show the contents'); string,
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!', { goodbye: 'dummy', world: 'world' },
'if with non-empty array shows the contents'); 'GOODBYE cruel world!',
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!', 'if with string argument shows the contents'
'if with empty array does not show the contents'); );
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!', shouldCompileTo(
'if with zero does not show the contents'); string,
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!', { goodbye: false, world: 'world' },
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!', 'cruel world!',
'if with zero does not show the contents'); '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() { it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!', shouldCompileTo(
'if with function shows the contents when function returns true'); string,
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!', {
'if with function shows the contents when function returns string'); goodbye: function() {
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!', return true;
'if with function does not show the contents when returns false'); },
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!', world: 'world'
'if with function does not show the contents when returns undefined'); },
'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() { it('should not change the depth list', function() {
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; var string =
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!'); '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
shouldCompileTo(
string,
{ foo: { goodbye: true }, world: 'world' },
'GOODBYE cruel world!'
);
}); });
}); });
describe('#with', function() { describe('#with', function() {
it('with', function() { it('with', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}'; 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() { it('with with function argument', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}'; 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() { 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'); shouldCompileTo(string, {}, 'Person is not present');
}); });
it('with provides block parameter', function() { it('with provides block parameter', function() {
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}'; 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() { 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'); equals(result, 'Alan Johnson');
}); });
}); });
@@ -73,92 +165,179 @@ describe('builtin helpers', function() {
it('each', function() { it('each', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; var hash = {
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!', goodbyes: [
'each with array argument iterates over the contents when not empty'); { text: 'goodbye' },
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!', { text: 'Goodbye' },
'each with array argument ignores the contents when empty'); { 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() { it('each without data', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; var hash = {
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!'); goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , , false],
'goodbye! Goodbye! GOODBYE! cruel world!'
);
hash = {goodbyes: 'cruel', world: 'world'}; hash = { goodbyes: 'cruel', world: 'world' };
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld'); shouldCompileTo(
'{{#each .}}{{.}}{{/each}}',
[hash, , , , false],
'cruelworld'
);
}); });
it('each without context', function() { it('each without context', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
shouldCompileTo(string, [,,,, ], 'cruel !'); shouldCompileTo(string, [, , , ,], 'cruel !');
}); });
it('each with an object and @key', function() { 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() { function Clazz() {
this['<b>#1</b>'] = {text: 'goodbye'}; this['<b>#1</b>'] = { text: 'goodbye' };
this[2] = {text: 'GOODBYE'}; this[2] = { text: 'GOODBYE' };
} }
Clazz.prototype.foo = 'fail'; 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, // Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders // so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash); var actual = compileWithPartials(string, hash);
var expected1 = '&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!'; var expected1 =
var expected2 = '2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!'; '&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'); equals(
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!'); 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() { it('each with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); 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() { it('each with nested @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { 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 string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { it('each object with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { it('each with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -167,18 +346,34 @@ describe('builtin helpers', function() {
}); });
it('each with nested @first', 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 string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { it('each object with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -187,8 +382,16 @@ describe('builtin helpers', function() {
}); });
it('each with @last', function() { it('each with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -197,8 +400,12 @@ describe('builtin helpers', function() {
}); });
it('each object with @last', function() { it('each object with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -207,37 +414,78 @@ describe('builtin helpers', function() {
}); });
it('each with nested @last', 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 string =
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { it('each with function argument', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'}; var hash = {
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!', goodbyes: function() {
'each with array function argument iterates over the contents when not empty'); return [
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!', { text: 'goodbye' },
'each with array function argument ignores the contents when empty'); { 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() { it('each object when last key is an empty string', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var string =
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'}; '{{#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 template = CompilerContext.compile(string);
var result = template(hash); 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() { it('data passed to helpers', function() {
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'; 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 template = CompilerContext.compile(string);
var result = template(hash, { var result = template(hash, {
@@ -249,10 +497,16 @@ describe('builtin helpers', function() {
}); });
it('each on implicit context', function() { it('each on implicit context', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!'); function() {
var template = CompilerContext.compile(
'{{#each}}{{text}}! {{/each}}cruel world!'
);
template({}); template({});
}, handlebarsEnv.Exception, 'Must pass iterator to #each'); },
handlebarsEnv.Exception,
'Must pass iterator to #each'
);
}); });
if (global.Symbol && global.Symbol.iterator) { if (global.Symbol && global.Symbol.iterator) {
@@ -276,13 +530,25 @@ describe('builtin helpers', function() {
return new Iterator(this.arr); return new Iterator(this.arr);
}; };
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; 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 goodbyesEmpty = new Iterable([]);
var hash = {goodbyes: goodbyes, world: 'world'}; var hash = { goodbyes: goodbyes, world: 'world' };
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!', shouldCompileTo(
'each with array argument iterates over the contents when not empty'); string,
shouldCompileTo(string, {goodbyes: goodbyesEmpty, world: 'world'}, 'cruel world!', hash,
'each with array argument ignores the contents when empty'); '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; return;
} }
var $log, var $log, $info, $error;
$info,
$error;
beforeEach(function() { beforeEach(function() {
$log = console.log; $log = console.log;
$info = console.info; $info = console.info;
@@ -331,7 +595,7 @@ describe('builtin helpers', function() {
logArg = arg; logArg = arg;
}; };
shouldCompileTo(string, [hash,,,, {level: '03'}], ''); shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals('03', levelArg); equals('03', levelArg);
equals('whee', logArg); equals('whee', logArg);
}); });
@@ -367,7 +631,7 @@ describe('builtin helpers', function() {
console.error = $error; console.error = $error;
}; };
shouldCompileTo(string, [hash,,,, {level: '03'}], ''); shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called); equals(true, called);
}); });
it('should handle missing logger', function() { it('should handle missing logger', function() {
@@ -382,7 +646,7 @@ describe('builtin helpers', function() {
console.log = $log; console.log = $log;
}; };
shouldCompileTo(string, [hash,,,, {level: '03'}], ''); shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called); equals(true, called);
}); });
@@ -396,12 +660,12 @@ describe('builtin helpers', function() {
called = true; called = true;
}; };
shouldCompileTo(string, [hash,,,, {level: 'error'}], ''); shouldCompileTo(string, [hash, , , , { level: 'error' }], '');
equals(true, called); equals(true, called);
called = false; called = false;
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], ''); shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], '');
equals(true, called); equals(true, called);
}); });
it('should handle hash log levels', function() { it('should handle hash log levels', function() {
@@ -449,11 +713,10 @@ describe('builtin helpers', function() {
/* eslint-enable no-console */ /* eslint-enable no-console */
}); });
describe('#lookup', function() { describe('#lookup', function() {
it('should lookup arbitrary content', function() { it('should lookup arbitrary content', function() {
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}', 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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -462,7 +725,7 @@ describe('builtin helpers', function() {
}); });
it('should not fail on undefined value', function() { it('should not fail on undefined value', function() {
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}', 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 template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
+106 -29
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}}').equals(compile('{{foo}}')), true); equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), 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(
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true); compile('{{foo.bar baz "foo" true false bat=1}}').equals(
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true); 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() { it('should treat as not equal', function() {
equal(compile('foo').equals(compile('bar')), false); equal(compile('foo').equals(compile('bar')), false);
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}}').equals(compile('{{bar.bar}}')), false);
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false); equal(
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false); compile('{{foo.bar baz bat=1}}').equals(
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false); compile('{{foo.bar bar bat=1}}')
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false); ),
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() { describe('#compile', function() {
it('should fail with invalid input', function() { it('should fail with invalid input', function() {
shouldThrow(function() { shouldThrow(
function() {
Handlebars.compile(null); Handlebars.compile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'); },
shouldThrow(function() { Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function() {
Handlebars.compile({}); Handlebars.compile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'); },
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() { it('should include the location in the error (row and column)', function() {
try { try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')(); 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) { } 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) { 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, // 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) // 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() { it('should include the location as enumerable property', function() {
try { try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')(); 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) { } 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() { it('can utilize AST instance', function() {
equal(Handlebars.compile({ equal(
Handlebars.compile({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [{ type: 'ContentStatement', value: 'Hello' }]
})(), 'Hello'); })(),
'Hello'
);
}); });
it('can pass through an empty string', function() { 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() { 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)(); 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() { 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)(); 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() { describe('#precompile', function() {
it('should fail with invalid input', function() { it('should fail with invalid input', function() {
shouldThrow(function() { shouldThrow(
function() {
Handlebars.precompile(null); Handlebars.precompile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'); },
shouldThrow(function() { Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
);
shouldThrow(
function() {
Handlebars.precompile({}); Handlebars.precompile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'); },
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
);
}); });
it('can utilize AST instance', function() { it('can utilize AST instance', function() {
equal(/return "Hello"/.test(Handlebars.precompile({ equal(
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [{ type: 'ContentStatement', value: 'Hello' }]
})), true); })
),
true
);
}); });
it('can pass through an empty string', function() { it('can pass through an empty string', function() {
+134 -41
View File
@@ -1,6 +1,6 @@
describe('data', function() { describe('data', function() {
it('passing in data to a compiled function that expects data - works with helpers', 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 = { var helpers = {
hello: function(options) { 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'); equals('happy cat', result, 'Data output by helper');
}); });
@@ -19,7 +22,9 @@ describe('data', function() {
}); });
it('deep @foo triggers automatic top-level 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); var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
@@ -47,7 +52,11 @@ describe('data', function() {
}; };
var result = template({}, { helpers: helpers, data: { world: 'world' } }); 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() { 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' } }); 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() { 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' } } }); var result = template(
equals('Hello world', result, '@foo as a parameter retrieves template data'); {},
{ 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() { 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' } } }); var result = template(
equals('Hello undefined', result, '@foo as a parameter retrieves template data'); {},
{ 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() { it('parameter data throws when using complex scope references', function() {
@@ -96,17 +123,38 @@ describe('data', function() {
it('data can be functions', function() { it('data can be functions', function() {
var template = CompilerContext.compile('{{@hello}}'); var template = CompilerContext.compile('{{@hello}}');
var result = template({}, { data: { hello: function() { return 'hello'; } } }); var result = template(
{},
{
data: {
hello: function() {
return 'hello';
}
}
}
);
equals('hello', result); equals('hello', result);
}); });
it('data can be functions with params', function() { it('data can be functions with params', function() {
var template = CompilerContext.compile('{{@hello "hello"}}'); 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); equals('hello', result);
}); });
it('data is inherited downstream', function() { 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 = { var helpers = {
let: function(options) { let: function(options) {
var frame = Handlebars.createFrame(options.data); var frame = Handlebars.createFrame(options.data);
@@ -115,19 +163,22 @@ describe('data', function() {
frame[prop] = options.hash[prop]; 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'); 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() { 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 = { var partials = {
myPartial: CompilerContext.compile('{{hello}}', {data: true}) myPartial: CompilerContext.compile('{{hello}}', { data: true })
}; };
var helpers = { 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'); 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() { 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 = { var helpers = {
hello: function(noun, options) { 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'); equals('happy world!', result, 'Data output by helper');
}); });
it('passing in data to a compiled function that expects data - works with block helpers', function() { 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 = { var helpers = {
hello: function(options) { 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'); 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() { 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 = { var helpers = {
hello: function(options) { hello: function(options) {
return options.fn({exclaim: '?'}); return options.fn({ exclaim: '?' });
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || ''); 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'); 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() { 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 = { var helpers = {
hello: function(options) { hello: function(options) {
return options.data.accessData + ' ' + options.fn({exclaim: '?'}); return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || ''); 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'); equals('#win happy world?', result, 'Data output by helper');
}); });
it('you can override inherited data when invoking a helper', function() { 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 = { var helpers = {
hello: function(options) { 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) { world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || ''); 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'); equals('sad world?', result, 'Overriden data output by helper');
}); });
it('you can override inherited data when invoking a helper with depth', function() { 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 = { var helpers = {
hello: function(options) { hello: function(options) {
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} }); return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || ''); 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'); equals('sad world?', result, 'Overriden data output by helper');
}); });
describe('@root', function() { describe('@root', function() {
it('the root context can be looked up via @root', function() { it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile('{{@root.foo}}'); var template = CompilerContext.compile('{{@root.foo}}');
var result = template({foo: 'hello'}, { data: {} }); var result = template({ foo: 'hello' }, { data: {} });
equals('hello', result); equals('hello', result);
result = template({foo: 'hello'}, {}); result = template({ foo: 'hello' }, {});
equals('hello', result); equals('hello', result);
}); });
it('passed root values take priority', function() { it('passed root values take priority', function() {
var template = CompilerContext.compile('{{@root.foo}}'); var template = CompilerContext.compile('{{@root.foo}}');
var result = template({}, { data: {root: {foo: 'hello'} } }); var result = template({}, { data: { root: { foo: 'hello' } } });
equals('hello', result); equals('hello', result);
}); });
}); });
describe('nesting', function() { describe('nesting', function() {
it('the root context can be looked up via @root', function() { it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'); var template = CompilerContext.compile(
var result = template({foo: 'hello'}, { '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
);
var result = template(
{ foo: 'hello' },
{
helpers: { helpers: {
helper: function(options) { helper: function(options) {
var frame = Handlebars.createFrame(options.data); var frame = Handlebars.createFrame(options.data);
frame.depth = options.data.depth + 1; frame.depth = options.data.depth + 1;
return options.fn(this, {data: frame}); return options.fn(this, { data: frame });
} }
}, },
data: { data: {
depth: 0 depth: 0
} }
}); }
);
equals('2 1 0', result); equals('2 1 0', result);
}); });
}); });
+4 -2
View File
@@ -6,7 +6,6 @@ var fs = require('fs'),
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) { if (global.minimizedTest) {
filename = 'dist/handlebars.min.js'; 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); vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = { global.CompilerContext = {
+48 -18
View File
@@ -1,6 +1,6 @@
var global = (function() { var global = (function() {
return this; return this;
}()); })();
var AssertError; var AssertError;
if (Error.captureStackTrace) { if (Error.captureStackTrace) {
@@ -28,10 +28,19 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
/** /**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead * @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); var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) { 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 * @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/ */
global.compileWithPartials = function(string, hashOrArray, partials) { global.compileWithPartials = function(string, hashOrArray, partials) {
var template, var template, ary, options;
ary,
options;
if (hashOrArray && hashOrArray.hash) { if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray]; ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash; delete hashOrArray.hash;
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') { } else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
ary = []; ary = [];
ary.push(hashOrArray[0]); // input ary.push(hashOrArray[0]); // input
ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]}); ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]}; options =
typeof hashOrArray[3] === 'object'
? hashOrArray[3]
: { compat: hashOrArray[3] };
if (hashOrArray[4] != null) { if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4]; options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4]; ary[1].data = hashOrArray[4];
@@ -58,18 +68,23 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
ary = [hashOrArray]; ary = [hashOrArray];
} }
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options); template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
string,
options
);
return template.apply(this, ary); return template.apply(this, ary);
}; };
/** /**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`) * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/ * @see https://www.chaijs.com/api/bdd/
*/ */
global.equals = global.equal = function equals(a, b, msg) { global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) { 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)) { if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught); throw new AssertError('Type failure: ' + caught);
} }
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) { if (
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow); 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) { if (failed) {
@@ -95,18 +121,17 @@ global.shouldThrow = function(callback, type, msg) {
} }
}; };
global.expectTemplate = function(templateAsString) { global.expectTemplate = function(templateAsString) {
return new HandlebarsTestBench(templateAsString); return new HandlebarsTestBench(templateAsString);
}; };
function HandlebarsTestBench(templateAsString) { function HandlebarsTestBench(templateAsString) {
this.templateAsString = templateAsString; this.templateAsString = templateAsString;
this.helpers = {}; this.helpers = {};
this.partials = {}; this.partials = {};
this.input = {}; 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.compileOptions = {};
this.runtimeOptions = {}; this.runtimeOptions = {};
} }
@@ -146,7 +171,11 @@ HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
}; };
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) // 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; var self = this;
expect(function() { expect(function() {
self._compileAndExeute(); self._compileAndExeute();
@@ -154,7 +183,8 @@ HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg)
}; };
HandlebarsTestBench.prototype._compileAndExeute = function() { HandlebarsTestBench.prototype._compileAndExeute = function() {
var compile = Object.keys(this.partials).length > 0 var compile =
Object.keys(this.partials).length > 0
? CompilerContext.compileWithPartial ? CompilerContext.compileWithPartial
: CompilerContext.compile; : CompilerContext.compile;
-1
View File
@@ -3,7 +3,6 @@ require('./common');
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
+8 -4
View File
@@ -13,9 +13,14 @@ if (grep === '--min') {
grep = undefined; grep = undefined;
} }
var files = fs.readdirSync(testDir) var files = fs
.filter(function(name) { return (/.*\.js$/).test(name); }) .readdirSync(testDir)
.map(function(name) { return testDir + path.sep + name; }); .filter(function(name) {
return /.*\.js$/.test(name);
})
.map(function(name) {
return testDir + path.sep + name;
});
if (global.minimizedTest) { if (global.minimizedTest) {
run('./runtime', function() { run('./runtime', function() {
@@ -37,7 +42,6 @@ if (global.minimizedTest) {
}); });
} }
function run(env, callback) { function run(env, callback) {
var mocha = new Mocha(); var mocha = new Mocha();
mocha.ui('bdd'); mocha.ui('bdd');
+9 -3
View File
@@ -6,7 +6,6 @@ var fs = require('fs'),
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) { if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js'; 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 parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler'); var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
@@ -30,7 +32,11 @@ global.CompilerContext = {
compile: function(template, options) { compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests // Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) { handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv); return compiler.precompile(
precompileTemplate,
precompileOptions,
handlebarsEnv
);
}; };
handlebarsEnv.parse = parse; handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler; handlebarsEnv.Compiler = compiler.Compiler;
+712 -216
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() { it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) { handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(
parent,
name
) {
return parent + '.bar_' + name; return parent + '.bar_' + name;
}; };
/* eslint-disable camelcase */ /* eslint-disable camelcase */
@@ -43,7 +46,7 @@ describe('javascript-compiler api', function() {
}; };
handlebarsEnv.VM.checkRevision = function(compilerInfo) { handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') { if (compilerInfo !== 'crazy') {
throw new Error('It didn\'t work'); throw new Error("It didn't work");
} }
}; };
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food '); shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
@@ -54,7 +57,8 @@ describe('javascript-compiler api', function() {
beforeEach(function() { beforeEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true; handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer; $superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer; $superCreate =
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
}); });
afterEach(function() { afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false; handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
@@ -69,7 +73,9 @@ describe('javascript-compiler api', function() {
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food '); shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
}); });
it('should allow append buffer override', function() { 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"']); return $superAppend.call(this, [string, ' + "_foo"']);
}; };
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo'); shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
+237 -75
View File
@@ -68,7 +68,10 @@ describe('parser', function() {
equals(astFor('{{null}}'), '{{ NULL [] }}\n'); equals(astFor('{{null}}'), '{{ NULL [] }}\n');
}); });
it('parses mustaches with undefined and null parameters', function() { 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() { it('parses mustaches with DATA parameters', function() {
@@ -78,23 +81,53 @@ describe('parser', function() {
it('parses mustaches with hash arguments', function() { it('parses mustaches with hash arguments', function() {
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n'); 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=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
equals(astFor('{{foo bar=true}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'); equals(
equals(astFor('{{foo bar=false}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'); astFor('{{foo bar=true}}'),
equals(astFor('{{foo bar=@baz}}'), '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'); '{{ 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(
equals(astFor('{{foo bar=baz bat="bam"}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'); 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 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(
equals(astFor('{{foo omg bar=baz bat="bam" baz=1}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'); astFor('{{foo omg bar=baz bat="bam"}}'),
equals(astFor('{{foo omg bar=baz bat="bam" baz=true}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'); '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\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" 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() { 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() { it('parses a partial', function() {
@@ -108,47 +141,81 @@ describe('parser', function() {
}); });
it('parses a partial with hash', 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() { 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() { 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() { 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() { it('should handle parser block mismatch', function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('{{#> goodbyes}}{{/hellos}}'); astFor('{{#> goodbyes}}{{/hellos}}');
}, Error, (/goodbyes doesn't match hellos/)); },
Error,
/goodbyes doesn't match hellos/
);
}); });
it('parsers partial blocks with arguments', function() { 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() { 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() { 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() { 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() { 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() { 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() { it('parses empty blocks', function() {
@@ -156,31 +223,52 @@ describe('parser', function() {
}); });
it('parses empty blocks with empty inverse section', 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() { 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() { 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() { 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() { 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() { 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() { 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() { it('throws on old inverse section', function() {
shouldThrow(function() { shouldThrow(function() {
@@ -189,105 +277,179 @@ describe('parser', function() {
}); });
it('parses block with block params', 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() { 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() { 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() { it("raises if there's a Parse error", function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('foo{{^}}bar'); astFor('foo{{^}}bar');
}, Error, /Parse error on line 1/); },
shouldThrow(function() { Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo}'); astFor('{{foo}');
}, Error, /Parse error on line 1/); },
shouldThrow(function() { Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo &}}'); astFor('{{foo &}}');
}, Error, /Parse error on line 1/); },
shouldThrow(function() { Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{#goodbyes}}{{/hellos}}'); astFor('{{#goodbyes}}{{/hellos}}');
}, Error, /goodbyes doesn't match hellos/); },
Error,
/goodbyes doesn't match hellos/
);
shouldThrow(function() { shouldThrow(
function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}'); astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
}, Error, /goodbyes doesn't match hellos/); },
Error,
/goodbyes doesn't match hellos/
);
}); });
it('should handle invalid paths', function() { it('should handle invalid paths', function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('{{foo/../bar}}'); astFor('{{foo/../bar}}');
}, Error, /Invalid path: foo\/\.\. - 1:2/); },
shouldThrow(function() { Error,
/Invalid path: foo\/\.\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/./bar}}'); astFor('{{foo/./bar}}');
}, Error, /Invalid path: foo\/\. - 1:2/); },
shouldThrow(function() { Error,
/Invalid path: foo\/\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/this/bar}}'); astFor('{{foo/this/bar}}');
}, Error, /Invalid path: foo\/this - 1:2/); },
Error,
/Invalid path: foo\/this - 1:2/
);
}); });
it('knows how to report the correct line number in errors', function() { it('knows how to report the correct line number in errors', function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('hello\nmy\n{{foo}'); astFor('hello\nmy\n{{foo}');
}, Error, /Parse error on line 3/); },
shouldThrow(function() { Error,
/Parse error on line 3/
);
shouldThrow(
function() {
astFor('hello\n\nmy\n\n{{foo}'); astFor('hello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 5/); },
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() { it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('\n\nhello\n\nmy\n\n{{foo}'); astFor('\n\nhello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 7/); },
Error,
/Parse error on line 7/
);
}); });
describe('externally compiled AST', function() { describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() { it('can pass through an already-compiled AST', function() {
equals(astFor({ equals(
astFor({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [{ type: 'ContentStatement', value: 'Hello' }]
}), 'CONTENT[ \'Hello\' ]\n'); }),
"CONTENT[ 'Hello' ]\n"
);
}); });
}); });
describe('directives', function() { describe('directives', function() {
it('should parse block 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() { it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n'); equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
}); });
it('should fail if directives have inverse', function() { it('should fail if directives have inverse', function() {
shouldThrow(function() { shouldThrow(
function() {
astFor('{{#* foo}}{{^}}{{/foo}}'); astFor('{{#* foo}}{{^}}{{/foo}}');
}, Error, /Unexpected inverse/); },
Error,
/Unexpected inverse/
);
}); });
}); });
it('GH1024 - should track program location properly', function() { it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse('\n' var p = Handlebars.parse(
+ ' {{#if foo}}\n' '\n' +
+ ' {{bar}}\n' ' {{#if foo}}\n' +
+ ' {{else}} {{baz}}\n' ' {{bar}}\n' +
+ '\n' ' {{else}} {{baz}}\n' +
+ ' {{/if}}\n' '\n' +
+ ' '); ' {{/if}}\n' +
' '
);
// We really need a deep equals but for now this should be stable... // We really need a deep equals but for now this should be stable...
equals(JSON.stringify(p.loc), JSON.stringify({ equals(
JSON.stringify(p.loc),
JSON.stringify({
start: { line: 1, column: 0 }, start: { line: 1, column: 0 },
end: { line: 7, column: 4 } end: { line: 7, column: 4 }
})); })
equals(JSON.stringify(p.body[1].program.loc), JSON.stringify({ );
equals(
JSON.stringify(p.body[1].program.loc),
JSON.stringify({
start: { line: 2, column: 13 }, start: { line: 2, column: 13 },
end: { line: 4, column: 7 } end: { line: 4, column: 7 }
})); })
equals(JSON.stringify(p.body[1].inverse.loc), JSON.stringify({ );
equals(
JSON.stringify(p.body[1].inverse.loc),
JSON.stringify({
start: { line: 4, column: 15 }, start: { line: 4, column: 15 },
end: { line: 6, column: 5 } end: { line: 6, column: 5 }
})); })
);
}); });
}); });
+477 -135
View File
@@ -2,115 +2,224 @@ describe('partials', function() {
it('basic partials', function() { it('basic partials', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); dudes: [
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); { 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() { it('dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; 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 = { var helpers = {
partial: function() { partial: function() {
return 'dude'; return 'dude';
} }
}; };
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); shouldCompileToWithPartials(
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); 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() { it('failing dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; 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 = { var helpers = {
partial: function() { partial: function() {
return 'missing'; return 'missing';
} }
}; };
shouldThrow(function() { shouldThrow(
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) '); function() {
}, Handlebars.Exception, 'The partial missing could not be found'); 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() { it('partials with context', function() {
var string = 'Dudes: {{>dude dudes}}'; var string = 'Dudes: {{>dude dudes}}';
var partial = '{{#this}}{{name}} ({{url}}) {{/this}}'; var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ', dudes: [
'Partials can be passed a context'); { 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() { it('partials with no context', function() {
var partial = '{{name}} ({{url}}) '; 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( shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}', 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}], [hash, {}, { dude: partial }, { explicitPartialContext: true }],
true, true,
'Dudes: () () '); 'Dudes: () () '
);
shouldCompileToWithPartials( shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}', 'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}], [hash, {}, { dude: partial }, { explicitPartialContext: true }],
true, true,
'Dudes: foo () foo () '); 'Dudes: foo () foo () '
);
}); });
it('partials with string context', function() { it('partials with string context', function() {
var string = 'Dudes: {{>dude "dudes"}}'; var string = 'Dudes: {{>dude "dudes"}}';
var partial = '{{.}}'; var partial = '{{.}}';
var hash = {}; var hash = {};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: dudes'); shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: dudes'
);
}); });
it('partials with undefined context', function() { it('partials with undefined context', function() {
var string = 'Dudes: {{>dude dudes}}'; var string = 'Dudes: {{>dude dudes}}';
var partial = '{{foo}} Empty'; var partial = '{{foo}} Empty';
var hash = {}; var hash = {};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Empty'); shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Empty'
);
}); });
it('partials with duplicate parameters', function() { it('partials with duplicate parameters', function() {
shouldThrow(function() { shouldThrow(
function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}'); CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
}, Error, 'Unsupported number of partial arguments: 2 - 1:7'); },
Error,
'Unsupported number of partial arguments: 2 - 1:7'
);
}); });
it('partials with parameters', function() { it('partials with parameters', function() {
var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
var partial = '{{others.foo}}{{name}} ({{url}}) '; var partial = '{{others.foo}}{{name}} ({{url}}) ';
var hash = {foo: 'bar', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ', foo: 'bar',
'Basic partials output based on current context.'); 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() { it('partial in a partial', function() {
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
var dude = '{{name}} {{> url}} '; var dude = '{{name}} {{> url}} ';
var url = '<a href="{{url}}">{{url}}</a>'; var url = '<a href="{{url}}">{{url}}</a>';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
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'); 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() { it('rendering undefined partial throws an exception', function() {
shouldThrow(function() { shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}'); var template = CompilerContext.compile('{{> whatever}}');
template(); template();
}, Handlebars.Exception, 'The partial whatever could not be found'); },
Handlebars.Exception,
'The partial whatever could not be found'
);
}); });
it('registering undefined partial throws an exception', function() { it('registering undefined partial throws an exception', function() {
shouldThrow(function() { shouldThrow(
function() {
var undef; var undef;
handlebarsEnv.registerPartial('undefined_test', undef); handlebarsEnv.registerPartial('undefined_test', undef);
}, Handlebars.Exception, 'Attempting to register a partial called "undefined_test" as undefined'); },
Handlebars.Exception,
'Attempting to register a partial called "undefined_test" as undefined'
);
}); });
it('rendering template partial in vm mode throws an exception', function() { it('rendering template partial in vm mode throws an exception', function() {
shouldThrow(function() { shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}'); var template = CompilerContext.compile('{{> whatever}}');
template(); template();
}, Handlebars.Exception, 'The partial whatever could not be found'); },
Handlebars.Exception,
'The partial whatever could not be found'
);
}); });
it('rendering function partial in vm mode', function() { it('rendering function partial in vm mode', function() {
@@ -118,30 +227,57 @@ describe('partials', function() {
function partial(context) { function partial(context) {
return context.name + ' (' + context.url + ') '; return context.name + ' (' + context.url + ') ';
} }
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileTo(string, [hash, {}, {dude: partial}], 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ', dudes: [
'Function partials output based in VM.'); { 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() { it('GH-14: a partial preceding a selector', function() {
var string = 'Dudes: {{>dude}} {{anotherDude}}'; var string = 'Dudes: {{>dude}} {{anotherDude}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, 'Dudes: Jeepers Creepers', 'Regular selectors can follow a partial'); shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes: Jeepers Creepers',
'Regular selectors can follow a partial'
);
}); });
it('Partials with slash paths', function() { it('Partials with slash paths', function() {
var string = 'Dudes: {{> shared/dude}}'; var string = 'Dudes: {{> shared/dude}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths'); shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with slash and point paths', function() { it('Partials with slash and point paths', function() {
var string = 'Dudes: {{> shared/dude.thing}}'; var string = 'Dudes: {{> shared/dude.thing}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing': dude}], true, 'Dudes: Jeepers', 'Partials can use literal with points in paths'); shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude.thing': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal with points in paths'
);
}); });
it('Global Partials', function() { it('Global Partials', function() {
@@ -149,8 +285,14 @@ describe('partials', function() {
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed'); shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
handlebarsEnv.unregisterPartial('globalTest'); handlebarsEnv.unregisterPartial('globalTest');
equals(handlebarsEnv.partials.globalTest, undefined); equals(handlebarsEnv.partials.globalTest, undefined);
@@ -163,51 +305,95 @@ describe('partials', function() {
}); });
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed'); shouldCompileToWithPartials(
string,
[hash],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
}); });
it('Partials with integer path', function() { it('Partials with integer path', function() {
var string = 'Dudes: {{> 404}}'; var string = 'Dudes: {{> 404}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {404: dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths'); shouldCompileToWithPartials(
string,
[hash, {}, { 404: dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with complex path', function() { it('Partials with complex path', function() {
var string = 'Dudes: {{> 404/asdf?.bar}}'; var string = 'Dudes: {{> 404/asdf?.bar}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths'); shouldCompileToWithPartials(
string,
[hash, {}, { '404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with escaped', function() { it('Partials with escaped', function() {
var string = 'Dudes: {{> [+404/asdf?.bar]}}'; var string = 'Dudes: {{> [+404/asdf?.bar]}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths'); shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with string', function() { it('Partials with string', function() {
var string = 'Dudes: {{> \'+404/asdf?.bar\'}}'; var string = "Dudes: {{> '+404/asdf?.bar'}}";
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths'); shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('should handle empty partial', function() { it('should handle empty partial', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = ''; var partial = '';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: '); dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: '
);
}); });
it('throw on missing partial', function() { it('throw on missing partial', function() {
var compile = handlebarsEnv.compile; var compile = handlebarsEnv.compile;
handlebarsEnv.compile = undefined; handlebarsEnv.compile = undefined;
shouldThrow(function() { shouldThrow(
shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], ''); function() {
}, Error, /The partial dude could not be compiled/); shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], '');
},
Error,
/The partial dude could not be compiled/
);
handlebarsEnv.compile = compile; handlebarsEnv.compile = compile;
}); });
@@ -217,224 +403,309 @@ describe('partials', function() {
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {}], [{}, {}, {}],
true, true,
'success'); 'success'
);
}); });
it('should execute default block with proper context', function() { it('should execute default block with proper context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude context}}{{value}}{{/dude}}', '{{#> dude context}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {}], [{ context: { value: 'success' } }, {}, {}],
true, true,
'success'); 'success'
);
}); });
it('should propagate block parameters to default block', function() { it('should propagate block parameters to default block', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {}], [{ context: { value: 'success' } }, {}, {}],
true, true,
'success'); 'success'
);
}); });
it('should not use partial block if partial exists', function() { it('should not use partial block if partial exists', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}fail{{/dude}}', '{{#> dude}}fail{{/dude}}',
[{}, {}, {dude: 'success'}], [{}, {}, { dude: 'success' }],
true, true,
'success'); 'success'
);
}); });
it('should render block from partial', function() { it('should render block from partial', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }}'}], [{}, {}, { dude: '{{> @partial-block }}' }],
true, true,
'success'); 'success'
);
}); });
it('should be able to render the partial-block twice', function() { it('should be able to render the partial-block twice', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}], [{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }],
true, true,
'success success'); 'success success'
);
}); });
it('should render block from partial with context', function() { it('should render block from partial with context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}', '{{#> dude}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}], [
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true, true,
'success'); 'success'
);
}); });
it('should be able to access the @data frame from a partial-block', function() { it('should be able to access the @data frame from a partial-block', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}in-block: {{@root/value}}{{/dude}}', '{{#> 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, 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() { it('should allow the #each-helper to be used along with partial-blocks', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'<template>{{#> list value}}value = {{.}}{{/list}}</template>', '<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, 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() { it('should render block from partial with context (twice)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}', '{{#> dude}}{{value}}{{/dude}}',
[ [
{context: {value: 'success'}}, { context: { value: 'success' } },
{}, {},
{ {
dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' dude:
'{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
} }
], ],
true, true,
'success success'); 'success success'
);
}); });
it('should render block from partial with context', function() { it('should render block from partial with context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{../context/value}}{{/dude}}', '{{#> dude}}{{../context/value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}], [
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true, true,
'success'); 'success'
);
}); });
it('should render block from partial with block params', function() { it('should render block from partial with block params', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {dude: '{{> @partial-block }}'}], [
{ context: { value: 'success' } },
{},
{ dude: '{{> @partial-block }}' }
],
true, true,
'success'); 'success'
);
}); });
it('should render nested partial blocks', function() { it('should render nested partial blocks', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>', '<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>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, 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() { it('should render nested partial blocks at different nesting levels', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>', '<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>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, 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() { it('should render nested partial blocks at different nesting levels (twice)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>', '<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>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, 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() { it('should render nested partial blocks (twice at each level)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>', '<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>' nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
} }
], ],
true, true,
'<template><outer>' + '<template><outer>' +
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' + '<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>'); '</outer></template>'
);
}); });
}); });
describe('inline partials', function() { describe('inline partials', function() {
it('should define inline partials for template', 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() { 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() { it('should define inline partials for block', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success'); shouldCompileTo(
shouldThrow(function() { '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', {}, 'success'); {},
}, Error, /myPartial could not/); 'success'
);
shouldThrow(
function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
{},
'success'
);
},
Error,
/myPartial could not/
);
}); });
it('should override global partials', function() { 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() { 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() { 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() { it('should define inline partials for partial call', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}', '{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, {dude: '{{> myPartial }}'}], [{}, {}, { dude: '{{> myPartial }}' }],
true, true,
'success'); 'success'
);
}); });
it('should define inline partials in partial block call', function() { it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}', '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, {dude: '{{> myPartial }}'}], [{}, {}, { dude: '{{> myPartial }}' }],
true, true,
'success'); 'success'
);
}); });
it('should render nested inline partials', function() { it('should render nested inline partials', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' + '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' + '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{ value: 'success' }, {}, {}],
true, 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() { it('should render nested inline partials with partial-blocks on different nesting levels', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' + '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' + '{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{ value: 'success' }, {}, {}],
true, 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() { it('should render nested inline partials (twice at each level)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' + '{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' + '{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{ value: 'success' }, {}, {}],
true, 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) { if (Handlebars.compile) {
var env = Handlebars.create(); var env = Handlebars.create();
env.registerPartial('partial', '{{foo}}'); env.registerPartial('partial', '{{foo}}');
var template = env.compile('{{foo}} {{> partial}}', {noEscape: true}); var template = env.compile('{{foo}} {{> partial}}', { noEscape: true });
equal(template({foo: '<'}), '< <'); equal(template({ foo: '<' }), '< <');
} }
}); });
@@ -451,25 +722,52 @@ describe('partials', function() {
it('indented partials', function() { it('indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n'; var dude = '{{name}}\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, dudes: [
'Dudes:\n Yehuda\n Alan\n'); { 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() { it('nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}'; var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n'; var url = '{{url}}!\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, dudes: [
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'); { 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() { it('prevent nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}'; var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n'; var url = '{{url}}!\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}, {preventIndent: true}], true, dudes: [
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'); { 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() { it('partials can access parents', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '); 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() { it('partials can access parents with custom context', function() {
var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '); 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() { it('partials can access parents without data', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true, false], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '); 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() { it('partials inherit compat', function() {
var string = 'Dudes: {{> dude}}'; var string = 'Dudes: {{> dude}}';
var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}'; var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; var hash = {
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '); 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 '
);
}); });
}); });
}); });
+158 -65
View File
@@ -14,16 +14,13 @@ describe('precompiler', function() {
logFunction, logFunction,
errorLog, errorLog,
errorLogFunction, errorLogFunction,
precompile, precompile,
minify, minify,
emptyTemplate = { emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars', path: __dirname + '/artifacts/empty.handlebars',
name: 'empty', name: 'empty',
source: '' source: ''
}, },
file, file,
content, content,
writeFileSync; writeFileSync;
@@ -87,102 +84,166 @@ describe('precompiler', function() {
}); });
it('should output version', function() { it('should output version', function() {
Precompiler.cli({templates: [], version: true}); Precompiler.cli({ templates: [], version: true });
equals(log, Handlebars.VERSION); equals(log, Handlebars.VERSION);
}); });
it('should throw if lacking templates', function() { it('should throw if lacking templates', function() {
shouldThrow(function() { shouldThrow(
Precompiler.cli({templates: []}); function() {
}, Handlebars.Exception, 'Must define at least one template or directory.'); Precompiler.cli({ templates: [] });
},
Handlebars.Exception,
'Must define at least one template or directory.'
);
}); });
it('should handle empty/filtered directories', function() { it('should handle empty/filtered directories', function() {
Precompiler.cli({hasDirectory: true, templates: []}); Precompiler.cli({ hasDirectory: true, templates: [] });
// Success is not throwing // Success is not throwing
}); });
it('should throw when combining simple and minimized', function() { it('should throw when combining simple and minimized', function() {
shouldThrow(function() { shouldThrow(
Precompiler.cli({templates: [__dirname], simple: true, min: true}); function() {
}, Handlebars.Exception, 'Unable to minimize simple output'); 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() { it('should throw when combining simple and multiple templates', function() {
shouldThrow(function() { shouldThrow(
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true}); function() {
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode'); 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() { it('should throw when missing name', function() {
shouldThrow(function() { shouldThrow(
Precompiler.cli({templates: [{source: ''}], amd: true}); function() {
}, Handlebars.Exception, 'Name missing for template'); Precompiler.cli({ templates: [{ source: '' }], amd: true });
},
Handlebars.Exception,
'Name missing for template'
);
}); });
it('should throw when combining simple and directories', function() { it('should throw when combining simple and directories', function() {
shouldThrow(function() { shouldThrow(
Precompiler.cli({hasDirectory: true, templates: [1], simple: true}); function() {
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode'); Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
}); });
it('should output simple templates', function() { it('should output simple templates', function() {
Handlebars.precompile = function() { return 'simple'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], simple: true}); return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true });
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should default to simple templates', function() { it('should default to simple templates', function() {
Handlebars.precompile = function() { return 'simple'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [{source: ''}]}); return 'simple';
};
Precompiler.cli({ templates: [{ source: '' }] });
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should output amd templates', function() { it('should output amd templates', function() {
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], amd: true}); return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true });
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output multiple amd', function() { it('should output multiple amd', function() {
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'}); return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
namespace: 'foo'
});
equal(/templates = foo = foo \|\|/.test(log), true); equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true); equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output amd partials', function() { it('should output amd partials', function() {
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true}); return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
equal(/return Handlebars\.partials\['empty'\]/.test(log), true); equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output multiple amd partials', function() { it('should output multiple amd partials', function() {
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true}); return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
partial: true
});
equal(/return Handlebars\.partials\[/.test(log), false); equal(/return Handlebars\.partials\[/.test(log), false);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output commonjs templates', function() { it('should output commonjs templates', function() {
Handlebars.precompile = function() { return 'commonjs'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], commonjs: true}); return 'commonjs';
};
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
equal(/template\(commonjs\)/.test(log), true); equal(/template\(commonjs\)/.test(log), true);
}); });
it('should set data flag', function() { it('should set data flag', function() {
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; }; Handlebars.precompile = function(data, options) {
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true}); equal(options.data, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, data: true });
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should set known helpers', function() { it('should set known helpers', function() {
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; }; Handlebars.precompile = function(data, options) {
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'}); equal(options.knownHelpers.foo, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should output to file system', function() { it('should output to file system', function() {
Handlebars.precompile = function() { return 'simple'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'}); return 'simple';
};
Precompiler.cli({
templates: [emptyTemplate],
simple: true,
output: 'file!'
});
equal(file, 'file!'); equal(file, 'file!');
equal(content, 'simple\n'); equal(content, 'simple\n');
equal(log, ''); equal(log, '');
}); });
it('should output minimized templates', function() { it('should output minimized templates', function() {
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
uglify.minify = function() { return {code: 'min'}; }; return 'amd';
Precompiler.cli({templates: [emptyTemplate], min: true}); };
uglify.minify = function() {
return { code: 'min' };
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(log, 'min'); equal(log, 'min');
}); });
@@ -191,8 +252,10 @@ describe('precompiler', function() {
error.code = 'MODULE_NOT_FOUND'; error.code = 'MODULE_NOT_FOUND';
mockRequireUglify(error, function() { mockRequireUglify(error, function() {
var Precompiler = require('../dist/cjs/precompiler'); var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], min: true}); return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
equal(/\n/.test(log), true); equal(/\n/.test(log), true);
equal(/Code minimization is disabled/.test(errorLog), 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() { it('should fail on errors (other than missing module) while loading uglify-js', function() {
mockRequireUglify(new Error('Mock Error'), function() { mockRequireUglify(new Error('Mock Error'), function() {
shouldThrow(function() { shouldThrow(
function() {
var Precompiler = require('../dist/cjs/precompiler'); var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; }; Handlebars.precompile = function() {
Precompiler.cli({templates: [emptyTemplate], min: true}); return 'amd';
}, Error, 'Mock Error'); };
Precompiler.cli({ templates: [emptyTemplate], min: true });
},
Error,
'Mock Error'
);
}); });
}); });
it('should output map', function() { 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(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1); equal(log.match(/sourceMappingURL=/g).length, 1);
}); });
it('should output map', function() { 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(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1); equal(log.match(/sourceMappingURL=/g).length, 1);
@@ -225,35 +298,48 @@ describe('precompiler', function() {
describe('#loadTemplates', function() { describe('#loadTemplates', function() {
it('should throw on missing template', function(done) { 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"'); equal(err.message, 'Unable to open template file "foo"');
done(); done();
}); });
}); });
it('should enumerate directories by extension', function(done) { it('should enumerate directories by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hbs'}, function(err, opts) { Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
function(err, opts) {
equal(opts.templates.length, 1); equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2'); equal(opts.templates[0].name, 'example_2');
done(err); done(err);
}); }
);
}); });
it('should enumerate all templates by extension', function(done) { it('should enumerate all templates by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'handlebars'}, function(err, opts) { Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
function(err, opts) {
equal(opts.templates.length, 3); equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom'); equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty'); equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1'); equal(opts.templates[2].name, 'example_1');
done(err); done(err);
}); }
);
}); });
it('should handle regular expression characters in extensions', function(done) { it('should handle regular expression characters in extensions', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hb(s'}, function(err) { Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
function(err) {
// Success is not throwing // Success is not throwing
done(err); done(err);
}); }
);
}); });
it('should handle BOM', function(done) { 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) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].source, 'a'); equal(opts.templates[0].source, 'a');
done(err); done(err);
@@ -261,7 +347,11 @@ describe('precompiler', function() {
}); });
it('should handle different root', function(done) { 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) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, __dirname + '/artifacts/empty'); equal(opts.templates[0].name, __dirname + '/artifacts/empty');
done(err); done(err);
@@ -269,7 +359,7 @@ describe('precompiler', function() {
}); });
it('should accept string inputs', function(done) { it('should accept string inputs', function(done) {
var opts = {string: ''}; var opts = { string: '' };
Precompiler.loadTemplates(opts, function(err, opts) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, undefined); equal(opts.templates[0].name, undefined);
equal(opts.templates[0].source, ''); equal(opts.templates[0].source, '');
@@ -277,7 +367,7 @@ describe('precompiler', function() {
}); });
}); });
it('should accept string array inputs', function(done) { 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) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, 'beep'); equal(opts.templates[0].name, 'beep');
equal(opts.templates[0].source, ''); equal(opts.templates[0].source, '');
@@ -288,7 +378,7 @@ describe('precompiler', function() {
}); });
it('should accept stdin input', function(done) { it('should accept stdin input', function(done) {
var stdin = require('mock-stdin').stdin(); var stdin = require('mock-stdin').stdin();
Precompiler.loadTemplates({string: '-'}, function(err, opts) { Precompiler.loadTemplates({ string: '-' }, function(err, opts) {
equal(opts.templates[0].source, 'foo'); equal(opts.templates[0].source, 'foo');
done(err); done(err);
}); });
@@ -297,9 +387,12 @@ describe('precompiler', function() {
stdin.end(); stdin.end();
}); });
it('error on name missing', function(done) { it('error on name missing', function(done) {
var opts = {string: ['', 'bar']}; var opts = { string: ['', 'bar'] };
Precompiler.loadTemplates(opts, function(err) { 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(); done();
}); });
}); });
+218 -103
View File
@@ -1,93 +1,134 @@
describe('Regressions', function() { describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() { it('GH-94: Cannot read property of undefined', function() {
var data = { var data = {
'books': [{ books: [
'title': 'The origin of species', {
'author': { title: 'The origin of species',
'name': 'Charles Darwin' author: {
name: 'Charles Darwin'
} }
}, { },
'title': 'Lazarillo de Tormes' {
}] title: 'Lazarillo de Tormes'
}
]
}; };
var string = '{{#books}}{{title}}{{author.name}}{{/books}}'; var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
shouldCompileTo(string, data, 'The origin of speciesCharles DarwinLazarillo de Tormes', shouldCompileTo(
'Renders without an undefined property error'); 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() { it("GH-150: Inverted sections print when they shouldn't", function() {
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
shouldCompileTo(string, {}, 'not set :: ', "inverted sections run when property isn't present in context"); shouldCompileTo(
shouldCompileTo(string, {set: undefined}, 'not set :: ', 'inverted sections run when property is undefined'); string,
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"); '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() { it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}'; 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'); shouldCompileTo(string, data, '1, 2', 'it works as expected');
}); });
it("bug reported by @fat where lambdas weren't being properly resolved", function() { 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' var string =
+ '{{! Just ignore this business. }}\n' '<strong>This is a slightly more complicated {{thing}}.</strong>.\n' +
+ 'Check this out:\n' '{{! Just ignore this business. }}\n' +
+ '{{#hasThings}}\n' 'Check this out:\n' +
+ '<ul>\n' '{{#hasThings}}\n' +
+ '{{#things}}\n' '<ul>\n' +
+ '<li class={{className}}>{{word}}</li>\n' '{{#things}}\n' +
+ '{{/things}}</ul>.\n' '<li class={{className}}>{{word}}</li>\n' +
+ '{{/hasThings}}\n' '{{/things}}</ul>.\n' +
+ '{{^hasThings}}\n' '{{/hasThings}}\n' +
+ '\n' '{{^hasThings}}\n' +
+ '<small>Nothing to check out...</small>\n' '\n' +
+ '{{/hasThings}}'; '<small>Nothing to check out...</small>\n' +
'{{/hasThings}}';
var data = { var data = {
thing: function() { thing: function() {
return 'blah'; return 'blah';
}, },
things: [ things: [
{className: 'one', word: '@fat'}, { className: 'one', word: '@fat' },
{className: 'two', word: '@dhg'}, { className: 'two', word: '@dhg' },
{className: 'three', word: '@sayrer'} { className: 'three', word: '@sayrer' }
], ],
hasThings: function() { hasThings: function() {
return true; return true;
} }
}; };
var output = '<strong>This is a slightly more complicated blah.</strong>.\n' var output =
+ 'Check this out:\n' '<strong>This is a slightly more complicated blah.</strong>.\n' +
+ '<ul>\n' 'Check this out:\n' +
+ '<li class=one>@fat</li>\n' '<ul>\n' +
+ '<li class=two>@dhg</li>\n' '<li class=one>@fat</li>\n' +
+ '<li class=three>@sayrer</li>\n' '<li class=two>@dhg</li>\n' +
+ '</ul>.\n'; '<li class=three>@sayrer</li>\n' +
'</ul>.\n';
shouldCompileTo(string, data, output); shouldCompileTo(string, data, output);
}); });
it('GH-408: Multiple loops fail', function() { it('GH-408: Multiple loops fail', function() {
var context = [ var context = [
{ name: 'John Doe', location: { city: 'Chicago' } }, { 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); 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() { it('GS-428: Nested if else rendering', function() {
var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; var succeedingTemplate =
var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = { var helpers = {
blk: function(block) { return block.fn(''); }, blk: function(block) {
inverse: function(block) { return block.inverse(''); } return block.fn('');
},
inverse: function(block) {
return block.inverse('');
}
}; };
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
@@ -95,7 +136,7 @@ describe('Regressions', function() {
}); });
it('GH-458: Scoped this identifier', 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() { it('GH-375: Unicode line terminators', function() {
@@ -104,11 +145,11 @@ describe('Regressions', function() {
it('GH-534: Object prototype aliases', function() { it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Object.prototype[0xD834] = true; Object.prototype[0xd834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xD834]; delete Object.prototype[0xd834];
/* eslint-enable no-extend-native */ /* eslint-enable no-extend-native */
}); });
@@ -123,34 +164,46 @@ describe('Regressions', function() {
it('GH-676: Using array in escaping mustache fails', function() { it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}'; var string = '{{arr}}';
var data = { 'arr': [1, 2] }; var data = { arr: [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected'); shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
}); });
it('Mustache man page', function() { 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 = { var data = {
'name': 'Chris', name: 'Chris',
'value': 10000, value: 10000,
'taxed_value': 10000 - (10000 * 0.4), taxed_value: 10000 - 10000 * 0.4,
'in_ca': true 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() { 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() { 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() { it('GH-837: undefined values for helpers', function() {
var helpers = { var helpers = {
str: function(value) { return value + ''; } str: function(value) {
return value + '';
}
}; };
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined'); shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
@@ -159,15 +212,13 @@ describe('Regressions', function() {
it('GH-926: Depths and de-dupe', function() { it('GH-926: Depths and de-dupe', function() {
var context = { var context = {
name: 'foo', name: 'foo',
data: [ data: [1],
1 notData: [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); var result = template(context);
equals(result, 'foo'); equals(result, 'foo');
@@ -176,11 +227,15 @@ describe('Regressions', function() {
it('GH-1021: Each empty string key', function() { it('GH-1021: Each empty string key', function() {
var data = { var data = {
'': 'foo', '': 'foo',
'name': 'Chris', name: 'Chris',
'value': 10000 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() { 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() { it('GH-1065: Sparse arrays', function() {
var array = []; var array = [];
array[1] = 'foo'; array[1] = 'foo';
array[3] = 'bar'; 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() { it('GH-1093: Undefined helper context', function() {
var obj = {foo: undefined, bar: 'bat'}; var obj = { foo: undefined, bar: 'bat' };
var helpers = { var helpers = {
helper: function() { helper: function() {
// It's valid to execute a block against an undefined context, but // 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. // 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() { 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 = { var partials = {
doctype: 'doctype{{> content}}', 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() { it('GH-1089: should support failover content in multiple levels of inline partials', function() {
var string = '{{#> layout}}{{/layout}}'; var string = '{{#> layout}}{{/layout}}';
var partials = { var partials = {
doctype: 'doctype{{> content}}', 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() { it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}'; var string = '{{#> layout}}Outer{{/layout}}';
@@ -249,7 +330,7 @@ describe('Regressions', function() {
}); });
it('GH-1135 : Context handling within each iteration', function() { it('GH-1135 : Context handling within each iteration', function() {
var obj = {array: [1], name: 'John'}; var obj = { array: [1], name: 'John' };
var helpers = { var helpers = {
myif: function(conditional, options) { myif: function(conditional, options) {
if (conditional) { if (conditional) {
@@ -261,39 +342,51 @@ describe('Regressions', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#each array}}\n' '{{#each array}}\n' +
+ ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
+ ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
+ '{{/each}}', [obj, helpers], '{{/each}}',
' 1. IF: John--\n' [obj, helpers],
+ ' 2. MYIF: John==\n'); ' 1. IF: John--\n' + ' 2. MYIF: John==\n'
);
}); });
it('GH-1186: Support block params for existing programs', function() { it('GH-1186: Support block params for existing programs', function() {
var string = var string =
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
+ '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
+ '{{#>test }}{{#each listTwo 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() { it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; var string =
shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', ''); '{{#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() { it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
var string = 'template {{>partial}} template'; var string = 'template {{>partial}} template';
var partials = { var partials = {
partialWithBlock: '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', partialWithBlock:
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' 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() { 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() { it('should compile and execute templates', function() {
var newHandlebarsInstance = Handlebars.create(); var newHandlebarsInstance = Handlebars.create();
@@ -301,31 +394,53 @@ describe('Regressions', function() {
newHandlebarsInstance.registerHelper('loud', function(value) { newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase(); return value.toUpperCase();
}); });
var result = newHandlebarsInstance.templates['test.hbs']({name: 'yehuda'}); var result = newHandlebarsInstance.templates['test.hbs']({
name: 'yehuda'
});
equals(result.trim(), 'YEHUDA'); equals(result.trim(), 'YEHUDA');
}); });
it('should call "helperMissing" if a helper is missing', function() { it('should call "helperMissing" if a helper is missing', function() {
var newHandlebarsInstance = Handlebars.create(); var newHandlebarsInstance = Handlebars.create();
shouldThrow(function() { shouldThrow(
function() {
registerTemplate(newHandlebarsInstance); registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.templates['test.hbs']({}); newHandlebarsInstance.templates['test.hbs']({});
}, Handlebars.Exception, 'Missing helper: "loud"'); },
Handlebars.Exception,
'Missing helper: "loud"'
);
}); });
// This is a only slightly modified precompiled templated from compiled with 4.2.1 // This is a only slightly modified precompiled templated from compiled with 4.2.1
function registerTemplate(Handlebars) { function registerTemplate(Handlebars) {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; var template = Handlebars.template,
templates['test.hbs'] = template({'compiler': [7, '>= 4.0.0'], 'main': function(container, depth0, helpers, partials, data) { templates = (Handlebars.templates = Handlebars.templates || {});
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})) templates['test.hbs'] = template({
+ '\n\n'; compiler: [7, '>= 4.0.0'],
}, 'useData': true}); 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() { it('should allow hash with protected array names', function() {
var obj = {array: [1], name: 'John'}; var obj = { array: [1], name: 'John' };
var helpers = { var helpers = {
helpa: function(options) { helpa: function(options) {
return options.hash.length; return options.hash.length;
@@ -352,7 +467,7 @@ describe('Regressions', function() {
it('should only compile global partials once', function() { it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template'); var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({ newHandlebarsInstance.registerPartial({
'dude': 'I am a partial' dude: 'I am a partial'
}); });
var string = 'Dudes: {{> dude}} {{> dude}}'; var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once 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')); equal(template, require('./artifacts/example_1.handlebars'));
var expected = 'foo\n'; var expected = 'foo\n';
var result = template({foo: 'foo'}); var result = template({ foo: 'foo' });
equal(result, expected); equal(result, expected);
}); });
@@ -15,7 +15,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
equal(template, require('./artifacts/example_2.hbs')); equal(template, require('./artifacts/example_2.hbs'));
var expected = 'Hello, World!\n'; var expected = 'Hello, World!\n';
var result = template({name: 'World'}); var result = template({ name: 'World' });
equal(result, expected); equal(result, expected);
}); });
+49 -17
View File
@@ -1,34 +1,58 @@
describe('runtime', function() { describe('runtime', function() {
describe('#template', function() { describe('#template', function() {
it('should throw on invalid templates', function() { it('should throw on invalid templates', function() {
shouldThrow(function() { shouldThrow(
function() {
Handlebars.template({}); Handlebars.template({});
}, Error, 'Unknown template object: object'); },
shouldThrow(function() { Error,
'Unknown template object: object'
);
shouldThrow(
function() {
Handlebars.template(); Handlebars.template();
}, Error, 'Unknown template object: undefined'); },
shouldThrow(function() { Error,
'Unknown template object: undefined'
);
shouldThrow(
function() {
Handlebars.template(''); Handlebars.template('');
}, Error, 'Unknown template object: string'); },
Error,
'Unknown template object: string'
);
}); });
it('should throw on version mismatch', function() { it('should throw on version mismatch', function() {
shouldThrow(function() { shouldThrow(
function() {
Handlebars.template({ Handlebars.template({
main: {}, main: {},
compiler: [Handlebars.COMPILER_REVISION + 1] compiler: [Handlebars.COMPILER_REVISION + 1]
}); });
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/); },
shouldThrow(function() { Error,
/Template was precompiled with a newer version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({ Handlebars.template({
main: {}, main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1] compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
}); });
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/); },
shouldThrow(function() { Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({ Handlebars.template({
main: {} main: {}
}); });
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/); },
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
}); });
}); });
@@ -38,23 +62,31 @@ describe('runtime', function() {
} }
it('should throw for depthed methods without depths', function() { it('should throw for depthed methods without depths', function() {
shouldThrow(function() { shouldThrow(
function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}'); var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case. // Calling twice to hit the non-compiled case.
template._setup({}); template._setup({});
template._setup({}); template._setup({});
template._child(1); template._child(1);
}, Error, 'must pass parent depths'); },
Error,
'must pass parent depths'
);
}); });
it('should throw for block param methods without params', function() { it('should throw for block param methods without params', function() {
shouldThrow(function() { shouldThrow(
function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}'); var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case. // Calling twice to hit the non-compiled case.
template._setup({}); template._setup({});
template._setup({}); template._setup({});
template._child(1); template._child(1);
}, Error, 'must pass block params'); },
Error,
'must pass block params'
);
}); });
it('should expose child template', function() { it('should expose child template', function() {
var template = Handlebars.compile('{{#foo}}bar{{/foo}}'); var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
@@ -65,7 +97,7 @@ describe('runtime', function() {
it('should render depthed content', function() { it('should render depthed content', function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}'); 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, undefined, [], [{bar: 'baz'}])(), 'baz'); equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
}); });
}); });
+106 -42
View File
@@ -19,39 +19,58 @@ describe('security issues', function() {
.toCompileTo(''); .toCompileTo('');
}); });
it('should allow the "constructor" property to be accessed if it is enumerable', function() { it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo('{{constructor.name}}', {'constructor': { shouldCompileTo(
'name': 'here we go' '{{constructor.name}}',
}}, 'here we go'); {
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': { constructor: {
'name': 'here we go' name: 'here we go'
}}, '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() { it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': { shouldCompileTo(
'name': 'here we go' '{{lookup (lookup this "constructor") "name"}}',
}}, 'here we go'); {
constructor: {
name: 'here we go'
}
},
'here we go'
);
}); });
it('should allow prototype properties that are not constructors', function() { it('should allow prototype properties that are not constructors', function() {
function TestClass() { function TestClass() {}
}
Object.defineProperty(TestClass.prototype, 'abc', { Object.defineProperty(TestClass.prototype, 'abc', {
get: function() { get: function() {
return 'xyz'; return 'xyz';
} }
}); });
shouldCompileTo('{{#with this as |obj|}}{{obj.abc}}{{/with}}', shouldCompileTo(
new TestClass(), 'xyz'); '{{#with this as |obj|}}{{obj.abc}}{{/with}}',
shouldCompileTo('{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}', new TestClass(),
new TestClass(), 'xyz'); 'xyz'
);
shouldCompileTo(
'{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}',
new TestClass(),
'xyz'
);
}); });
}); });
@@ -69,7 +88,9 @@ describe('security issues', function() {
}); });
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() { shouldThrow(function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}'); var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({}); template({});
}, Error); }, Error);
}); });
@@ -77,14 +98,24 @@ describe('security issues', function() {
var functionCalls = []; var functionCalls = [];
expect(function() { expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }}); template({
fn: function() {
functionCalls.push('called');
}
});
}).to.throw(Error); }).to.throw(Error);
expect(functionCalls.length).to.equal(0); expect(functionCalls.length).to.equal(0);
}); });
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() { shouldThrow(function() {
var template = Handlebars.compile('{{#blockHelperMissing .}}{{/blockHelperMissing}}'); var template = Handlebars.compile(
template({ fn: function() { return 'functionInData';}}); '{{#blockHelperMissing .}}{{/blockHelperMissing}}'
);
template({
fn: function() {
return 'functionInData';
}
});
}, Error); }, Error);
}); });
}); });
@@ -92,35 +123,48 @@ describe('security issues', function() {
describe('with the option "allowCallsToHelperMissing" set to true', function() { describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', function() { it('should not throw an exception when calling "{{helperMissing}}" ', function() {
var template = Handlebars.compile('{{helperMissing}}'); var template = Handlebars.compile('{{helperMissing}}');
template({}, {allowCallsToHelperMissing: true}); template({}, { allowCallsToHelperMissing: true });
}); });
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}'); var template = Handlebars.compile(
template({}, {allowCallsToHelperMissing: true}); '{{#helperMissing}}{{/helperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
}); });
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = []; var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true}); template(
{
fn: function() {
functionCalls.push('called');
}
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1); equals(functionCalls.length, 1);
}); });
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'); var template = Handlebars.compile(
template({}, {allowCallsToHelperMissing: true}); '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
}); });
}); });
}); });
describe('GH-1563', function() { describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() { it('should not allow to access constructor after overriding via __defineGetter__', function() {
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) { if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway return this.skip(); // Browser does not support this exploit anyway
} }
expectTemplate('{{__defineGetter__ "undefined" valueOf }}' + expectTemplate(
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' + '{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' + '{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' + '{{constructor.name}}' +
'{{/with}}') '{{/with}}'
)
.withInput({}) .withInput({})
.toThrow(/Missing helper: "__defineGetter__"/); .toThrow(/Missing helper: "__defineGetter__"/);
}); });
@@ -128,17 +172,37 @@ describe('security issues', function() {
describe('GH-1595', function() { describe('GH-1595', function() {
it('properties, that are required to be enumerable', function() { it('properties, that are required to be enumerable', function() {
expectTemplate('{{constructor}}').withInput({}).toCompileTo(''); expectTemplate('{{constructor}}')
expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo(''); .withInput({})
expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo(''); .toCompileTo('');
expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo(''); expectTemplate('{{__defineGetter__}}')
expectTemplate('{{__proto__}}').withInput({}).toCompileTo(''); .withInput({})
.toCompileTo('');
expectTemplate('{{__defineSetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__lookupGetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__proto__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo(''); expectTemplate('{{lookup "constructor"}}')
expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo(''); .withInput({})
expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo(''); .toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo(''); expectTemplate('{{lookup "__defineGetter__"}}')
expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo(''); .withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__defineSetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__proto__"}}')
.withInput({})
.toCompileTo('');
}); });
}); });
}); });
+10 -4
View File
@@ -13,20 +13,26 @@ describe('source-map', function() {
} }
it('should safely include source map info', 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.code, true);
equal(!!template.map, !CompilerContext.browser); equal(!!template.map, !CompilerContext.browser);
}); });
it('should map source properly', function() { it('should map source properly', function() {
var templateSource = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}', var templateSource =
template = Handlebars.precompile(templateSource, {destName: 'dest.js', srcName: 'src.hbs'}); ' 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) { if (template.map) {
var consumer = new SourceMapConsumer(template.map), var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'), lines = template.code.split('\n'),
srcLines = templateSource.split('\n'), srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines), generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines); source = grepLine(' b', srcLines);
+23 -11
View File
@@ -9,7 +9,7 @@ describe('spec', function() {
var specDir = __dirname + '/mustache/specs/'; var specDir = __dirname + '/mustache/specs/';
var specs = _.filter(fs.readdirSync(specDir), function(name) { var specs = _.filter(fs.readdirSync(specDir), function(name) {
return (/.*\.json$/).test(name); return /.*\.json$/.test(name);
}); });
_.each(specs, function(name) { _.each(specs, function(name) {
@@ -17,16 +17,17 @@ describe('spec', function() {
_.each(spec.tests, function(test) { _.each(spec.tests, function(test) {
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec // Our lambda implementation knowingly deviates from the optional Mustace lambda spec
// We also do not support alternative delimeters // We also do not support alternative delimeters
if (name === '~lambdas.json' if (
name === '~lambdas.json' ||
// We also choose to throw if paritals are not found // We also choose to throw if paritals are not found
|| (name === 'partials.json' && test.name === 'Failed Lookup') (name === 'partials.json' && test.name === 'Failed Lookup') ||
// We nest the entire response from partials, not just the literals // We nest the entire response from partials, not just the literals
|| (name === 'partials.json' && test.name === 'Standalone Indentation') (name === 'partials.json' && test.name === 'Standalone Indentation') ||
/\{\{=/.test(test.template) ||
|| (/\{\{=/).test(test.template) _.any(test.partials, function(partial) {
|| _.any(test.partials, function(partial) { return (/\{\{=/).test(partial); })) { return /\{\{=/.test(partial);
})
) {
it.skip(name + ' - ' + test.name); it.skip(name + ' - ' + test.name);
return; return;
} }
@@ -40,9 +41,20 @@ describe('spec', function() {
} }
it(name + ' - ' + test.name, function() { it(name + ' - ' + test.name, function() {
if (test.partials) { 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 { } else {
shouldCompileTo(test.template, [data, {}, {}, true], test.expected, test.desc + ' "' + test.template + '"'); shouldCompileTo(
test.template,
[data, {}, {}, true],
test.expected,
test.desc + ' "' + test.template + '"'
);
} }
}); });
}); });
+119 -48
View File
@@ -3,43 +3,58 @@ var Exception = Handlebars.Exception;
describe('strict', function() { describe('strict', function() {
describe('strict mode', function() { describe('strict mode', function() {
it('should error on missing property lookup', function() { it('should error on missing property lookup', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{hello}}', {strict: true}); function() {
var template = CompilerContext.compile('{{hello}}', { strict: true });
template({}); template({});
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
}); });
it('should error on missing child', function() { it('should error on missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true}); var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({hello: {bar: 'foo'}}), 'foo'); equals(template({ hello: { bar: 'foo' } }), 'foo');
shouldThrow(function() { shouldThrow(
template({hello: {}}); function() {
}, Exception, /"bar" not defined in/); template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
}); });
it('should handle explicit undefined', function() { 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() { it('should error on missing property lookup in known helpers mode', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true}); function() {
var template = CompilerContext.compile('{{hello}}', {
strict: true,
knownHelpersOnly: true
});
template({}); template({});
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
}); });
it('should error on missing context', function() { it('should error on missing context', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true}); var template = CompilerContext.compile('{{hello}}', { strict: true });
template(); template();
}, Error); }, Error);
}); });
it('should error on missing data lookup', function() { it('should error on missing data lookup', function() {
var template = CompilerContext.compile('{{@hello}}', {strict: true}); var template = CompilerContext.compile('{{@hello}}', { strict: true });
equals(template(undefined, {data: {hello: 'foo'}}), 'foo'); equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
shouldThrow(function() { shouldThrow(function() {
template(); template();
@@ -47,45 +62,81 @@ describe('strict', function() {
}); });
it('should not run helperMissing for helper calls', function() { it('should not run helperMissing for helper calls', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{hello foo}}', {strict: true}); function() {
var template = CompilerContext.compile('{{hello foo}}', {
strict: true
});
template({foo: true}); template({ foo: true });
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true}); function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
strict: true
});
template({foo: true}); template({ foo: true });
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
}); });
it('should throw on ambiguous blocks', function() { it('should throw on ambiguous blocks', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true}); function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
strict: true
});
template({}); template({});
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true}); function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
strict: true
});
template({}); template({});
}, Exception, /"hello" not defined in/); },
Exception,
/"hello" not defined in/
);
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true}); function() {
var template = CompilerContext.compile(
'{{#hello.bar}}{{/hello.bar}}',
{ strict: true }
);
template({hello: {}}); template({ hello: {} });
}, Exception, /"bar" not defined in/); },
Exception,
/"bar" not defined in/
);
}); });
it('should allow undefined parameters when passed to helpers', function() { 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'); equals(template({}), 'success');
}); });
it('should allow undefined hash when passed to helpers', function() { 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 = { var helpers = {
helper: function(options) { helper: function(options) {
equals('value' in options.hash, true); equals('value' in options.hash, true);
@@ -93,19 +144,27 @@ describe('strict', function() {
return 'success'; return 'success';
} }
}; };
equals(template({}, {helpers: helpers}), 'success'); equals(template({}, { helpers: helpers }), 'success');
}); });
it('should show error location on missing property lookup', function() { it('should show error location on missing property lookup', function() {
shouldThrow(function() { shouldThrow(
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true}); function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({}); template({});
}, Exception, '"hello" not defined in [object Object] - 4:5'); },
Exception,
'"hello" not defined in [object Object] - 4:5'
);
}); });
it('should error contains correct location properties on missing property lookup', function() { it('should error contains correct location properties on missing property lookup', function() {
try { try {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true}); var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({}); template({});
} catch (error) { } catch (error) {
equals(error.lineNumber, 4); equals(error.lineNumber, 4);
@@ -118,25 +177,33 @@ describe('strict', function() {
describe('assume objects', function() { describe('assume objects', function() {
it('should ignore missing property', function() { it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true}); var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
equal(template({}), ''); equal(template({}), '');
}); });
it('should ignore missing child', function() { 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() { it('should error on missing object', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true}); var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
template({}); template({});
}, Error); }, Error);
}); });
it('should error on missing context', function() { it('should error on missing context', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true}); var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
template(); template();
}, Error); }, Error);
@@ -144,14 +211,18 @@ describe('strict', function() {
it('should error on missing data lookup', function() { it('should error on missing data lookup', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true}); var template = CompilerContext.compile('{{@hello.bar}}', {
assumeObjects: true
});
template(); template();
}, Error); }, Error);
}); });
it('should execute blockHelperMissing', function() { 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'); equals(template({}), 'foo');
}); });
+104 -34
View File
@@ -1,6 +1,8 @@
describe('string params mode', function() { describe('string params mode', function() {
it('arguments to helpers can be retrieved from options hash in string form', 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 = { var helpers = {
wycats: function(passiveVoice, noun) { 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() { 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 = { var helpers = {
wycats: function(passiveVoice, noun, options) { wycats: function(passiveVoice, noun, options) {
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + return (
noun + ': ' + options.fn(this); '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() { 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 = { var helpers = {
tomdale: function(desire, noun, options) { tomdale: function(desire, noun, options) {
return 'STOP ME FROM READING HACKER NEWS I ' + return (
options.contexts[0][desire] + ' ' + noun; '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]); return options.fn(options.contexts[0][context]);
} }
}; };
var result = template({ var result = template(
{
dale: {}, dale: {},
need: 'need-a' need: 'need-a'
}, {helpers: helpers}); },
{ 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() { 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 = { var helpers = {
tomdale: function(desire, noun, trueBool, falseBool, options) { tomdale: function(desire, noun, trueBool, falseBool, options) {
equal(options.types[0], 'StringLiteral', 'the string type is passed'); equal(options.types[0], 'StringLiteral', 'the string type is passed');
equal(options.types[1], 'PathExpression', 'the expression type is passed'); equal(
equal(options.types[2], 'BooleanLiteral', 'the expression type is passed'); 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(desire, 'need', 'the string form is passed for strings');
equal(noun, 'dad.joke', 'the string form is passed for expressions'); equal(noun, 'dad.joke', 'the string form is passed for expressions');
equal(trueBool, true, 'raw booleans are passed through'); equal(trueBool, true, 'raw booleans are passed through');
@@ -72,7 +116,10 @@ describe('string params mode', function() {
}); });
it('hash parameters get type information', 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 = { var helpers = {
tomdale: function(exclamation, options) { tomdale: function(exclamation, options) {
@@ -94,9 +141,12 @@ describe('string params mode', function() {
}); });
it('hash parameters get context information', 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 = { var helpers = {
tomdale: function(exclamation, options) { tomdale: function(exclamation, options) {
@@ -110,7 +160,7 @@ describe('string params mode', function() {
equal(options.hash.bool, true); equal(options.hash.bool, true);
return 'Helper called'; return 'Helper called';
}, },
'with': function(withContext, options) { with: function(withContext, options) {
return options.fn(options.contexts[0][withContext]); 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() { 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 = { var helpers = {
tomdale: function(desire, noun, options) { tomdale: function(desire, noun, options) {
return 'STOP ME FROM READING HACKER NEWS I ' + return (
options.contexts[0][desire] + ' ' + noun + ' ' + 'STOP ME FROM READING HACKER NEWS I ' +
options.fn(this); options.contexts[0][desire] +
' ' +
noun +
' ' +
options.fn(this)
);
}, },
'with': function(context, options) { with: function(context, options) {
return options.fn(options.contexts[0][context]); return options.fn(options.contexts[0][context]);
} }
}; };
var result = template({ var result = template(
{
dale: {}, dale: {},
need: 'need-a' need: 'need-a'
}, {helpers: helpers}); },
{ 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() { 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 = { var helpers = {
'with': function() { with: function() {
return 'WITH'; return 'WITH';
}, },
view: function() { view: function() {
@@ -155,12 +223,14 @@ describe('string params mode', function() {
} }
}; };
var result = template({}, {helpers: helpers}); var result = template({}, { helpers: helpers });
equals(result, 'WITH'); equals(result, 'WITH');
}); });
it('should handle DATA', function() { it('should handle DATA', function() {
var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true }); var template = CompilerContext.compile('{{foo @bar}}', {
stringParams: true
});
var helpers = { var helpers = {
foo: function(bar, options) { foo: function(bar, options) {
+73 -19
View File
@@ -31,7 +31,7 @@ describe('subexpressions', function() {
it('mixed paths and helpers', function() { it('mixed paths and helpers', function() {
var string = '{{blog baz.bat (equal a b) baz.bar}}'; 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 = { var helpers = {
blog: function(val, that, theOther) { blog: function(val, that, theOther) {
return 'val is ' + val + ', ' + that + ' and ' + theOther; return 'val is ' + val + ', ' + that + ' and ' + theOther;
@@ -59,7 +59,7 @@ describe('subexpressions', function() {
}); });
it('GH-800 : Complex 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 = { var helpers = {
dash: function(a, b) { dash: function(a, b) {
return 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 d (concat a b)}}', [context, helpers], 'd-ab');
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab'); shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c'); 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() { 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 = { var helpers = {
input: function(options) { input: function(options) {
var hash = options.hash; var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); 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) { t: function(defaultString) {
return new Handlebars.SafeString(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() { 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 = { var context = {
item: { item: {
@@ -153,44 +169,82 @@ describe('subexpressions', function() {
var hash = options.hash; var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); 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) { t: function(defaultString) {
return new Handlebars.SafeString(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() { 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 = { var helpers = {
snog: function(a, b, options) { snog: function(a, b, options) {
equals(a, 'foo'); equals(a, 'foo');
equals(options.types.length, 2, 'string params for outer helper processed correctly'); equals(
equals(options.types[0], 'SubExpression', 'string params for outer helper processed correctly'); options.types.length,
equals(options.types[1], 'PathExpression', 'string params for outer helper processed correctly'); 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; return a + b;
}, },
blorg: function(a, options) { blorg: function(a, options) {
equals(options.types.length, 1, 'string params for inner helper processed correctly'); equals(
equals(options.types[0], 'PathExpression', 'string params for inner helper processed correctly'); 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; return a;
} }
}; };
var result = template({ var result = template(
{
foo: {}, foo: {},
yeah: {} yeah: {}
}, {helpers: helpers}); },
{ helpers: helpers }
);
equals(result, 'fooyeah'); equals(result, 'fooyeah');
}); });
it('as hashes in string params mode', function() { 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 = { var helpers = {
blog: function(options) { 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'); equals(result, 'val is BORK');
}); });
+393 -46
View File
@@ -26,7 +26,7 @@ describe('Tokenizer', function() {
if (!result || result === 'EOF' || result === 'INVALID') { if (!result || result === 'EOF' || result === 'INVALID') {
break; break;
} }
out.push({name: result, text: lexer.yytext}); out.push({ name: result, text: lexer.yytext });
} }
return out; return out;
@@ -55,7 +55,16 @@ describe('Tokenizer', function() {
it('supports escaping delimiters', function() { it('supports escaping delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} {{baz}}'); 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[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} '); shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -63,7 +72,14 @@ describe('Tokenizer', function() {
it('supports escaping multiple delimiters', function() { it('supports escaping multiple delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}'); 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[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} '); shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -72,14 +88,35 @@ describe('Tokenizer', function() {
it('supports escaping a triple stash', function() { it('supports escaping a triple stash', function() {
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}'); 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}}} '); shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
}); });
it('supports escaping escape character', function() { it('supports escaping escape character', function() {
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}'); 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[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar'); shouldBeToken(result[5], 'ID', 'bar');
@@ -87,7 +124,19 @@ describe('Tokenizer', function() {
it('supports escaping multiple escape characters', function() { it('supports escaping multiple escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}'); 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[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar'); shouldBeToken(result[5], 'ID', 'bar');
@@ -97,7 +146,18 @@ describe('Tokenizer', function() {
it('supports escaped mustaches after escaped escape characters', function() { it('supports escaped mustaches after escaped escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}'); 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[3], 'CONTENT', ' \\');
shouldBeToken(result[4], 'OPEN', '{{'); shouldBeToken(result[4], 'OPEN', '{{');
@@ -108,7 +168,17 @@ describe('Tokenizer', function() {
it('supports escaped escape characters after escaped mustaches', function() { it('supports escaped escape characters after escaped mustaches', function() {
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}'); 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[4], 'CONTENT', '{{bar}} ');
shouldBeToken(result[5], 'CONTENT', '\\'); shouldBeToken(result[5], 'CONTENT', '\\');
@@ -118,7 +188,19 @@ describe('Tokenizer', function() {
it('supports escaped escape character on a triple stash', function() { it('supports escaped escape character on a triple stash', function() {
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}'); 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[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar'); shouldBeToken(result[5], 'ID', 'bar');
@@ -133,7 +215,15 @@ describe('Tokenizer', function() {
var result = tokenize('{{foo.bar}}'); var result = tokenize('{{foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']); 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() { it('allows path literals with []', function() {
@@ -143,7 +233,18 @@ describe('Tokenizer', function() {
it('allows multiple path literals on a line with []', function() { it('allows multiple path literals on a line with []', function() {
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}'); 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() { it('allows escaped literals in []', function() {
@@ -158,13 +259,29 @@ describe('Tokenizer', function() {
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() { it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
var result = tokenize('{{../foo/bar}}'); 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', '..'); shouldBeToken(result[1], 'ID', '..');
}); });
it('tokenizes a path with .. as a parent path', function() { it('tokenizes a path with .. as a parent path', function() {
var result = tokenize('{{../foo.bar}}'); 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', '..'); shouldBeToken(result[1], 'ID', '..');
}); });
@@ -216,7 +333,15 @@ describe('Tokenizer', function() {
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() { it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo/bar.baz }}'); 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() { it('tokenizes partial block declarations', function() {
@@ -225,34 +350,69 @@ describe('Tokenizer', function() {
}); });
it('tokenizes a comment as "COMMENT"', function() { it('tokenizes a comment as "COMMENT"', function() {
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}'); 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 }}'); shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
}); });
it('tokenizes a block comment as "COMMENT"', function() { it('tokenizes a block comment as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}'); 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}} --}}'); shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
}); });
it('tokenizes a block comment with whitespace as "COMMENT"', function() { it('tokenizes a block comment with whitespace as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'); var result = tokenize(
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']); '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--}}'); 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() { it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
var result = tokenize('{{#foo}}content{{/foo}}'); 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() { it('tokenizes directives', function() {
shouldMatchTokens( shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
tokenize('{{#*foo}}content{{/foo}}'), 'OPEN_BLOCK',
['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']); 'ID',
shouldMatchTokens( 'CLOSE',
tokenize('{{*foo}}'), 'CONTENT',
['OPEN', 'ID', 'CLOSE']); 'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
}); });
it('tokenizes inverse sections as "INVERSE"', function() { it('tokenizes inverse sections as "INVERSE"', function() {
@@ -351,28 +511,98 @@ describe('Tokenizer', function() {
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize('{{ foo bar baz=bat }}'); 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 }}'); 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 }}'); 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 }}'); 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 }}'); 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" }}'); 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 }}'); 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"}}'); 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'); shouldBeToken(result[2], 'ID', 'omg');
}); });
@@ -386,7 +616,15 @@ describe('Tokenizer', function() {
shouldBeToken(result[3], 'ID', 'bar'); shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{ foo bar=@baz }}'); 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'); shouldBeToken(result[5], 'ID', 'baz');
}); });
@@ -400,12 +638,27 @@ describe('Tokenizer', function() {
it('tokenizes subexpressions', function() { it('tokenizes subexpressions', function() {
var result = tokenize('{{foo (bar)}}'); 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[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'bar'); shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{foo (a-x b-y)}}'); 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[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'a-x'); shouldBeToken(result[3], 'ID', 'a-x');
shouldBeToken(result[4], 'ID', 'b-y'); shouldBeToken(result[4], 'ID', 'b-y');
@@ -413,7 +666,21 @@ describe('Tokenizer', function() {
it('tokenizes nested subexpressions', function() { it('tokenizes nested subexpressions', function() {
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}'); 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[3], 'ID', 'bar');
shouldBeToken(result[5], 'ID', 'lol'); shouldBeToken(result[5], 'ID', 'lol');
shouldBeToken(result[6], 'ID', 'rofl'); shouldBeToken(result[6], 'ID', 'rofl');
@@ -421,29 +688,109 @@ describe('Tokenizer', function() {
}); });
it('tokenizes nested subexpressions: literals', function() { it('tokenizes nested subexpressions: literals', function() {
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}"); var result = tokenize(
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']); '{{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() { it('tokenizes block params', function() {
var result = tokenize('{{#foo as |bar|}}'); 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|}}'); 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 |}}'); 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 |}}'); 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|}}'); 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() { it('tokenizes raw blocks', function() {
var result = tokenize('{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'); var result = tokenize(
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']); '{{{{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'
]);
}); });
}); });
+250 -60
View File
@@ -1,7 +1,7 @@
describe('track ids', function() { describe('track ids', function() {
var context; var context;
beforeEach(function() { 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() { 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() { 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 = { var helpers = {
wycats: function(passiveVoice, noun, options) { wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], 'is.a'); equal(options.ids[0], 'is.a');
equal(options.ids[1], 'slave.driver'); 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() { 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 = { var helpers = {
wycats: function(options) { wycats: function(options) {
equal(options.hashIds.bat, 'is.a'); equal(options.hashIds.bat, 'is.a');
equal(options.hashIds.baz, 'slave.driver'); 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() { 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 = { var helpers = {
wycats: function(passiveVoice, noun, thiz, thiz2, options) { wycats: function(passiveVoice, noun, thiz, thiz2, options) {
@@ -56,29 +88,57 @@ describe('track ids', function() {
equal(options.ids[2], 'is.a'); equal(options.ids[2], 'is.a');
equal(options.ids[3], ''); 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() { 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 = { var helpers = {
wycats: function(passiveVoice, noun, options) { wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], '@is.a'); equal(options.ids[0], '@is.a');
equal(options.ids[1], '@slave.driver'); 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() { 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 = { var helpers = {
wycats: function(passiveVoice, noun, options) { wycats: function(passiveVoice, noun, options) {
@@ -86,17 +146,31 @@ describe('track ids', function() {
equal(options.ids[1], null); equal(options.ids[1], null);
equal(options.hashIds.key, 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() { 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 = { var helpers = {
sub: function() { return 1; }, sub: function() {
return 1;
},
wycats: function(passiveVoice, options) { wycats: function(passiveVoice, options) {
equal(options.ids[0], true); 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() { 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 = { var helpers = {
doIt: function(options) { doIt: function(options) {
var blockParams = [this.is]; var blockParams = [this.is];
blockParams.path = ['zomg']; blockParams.path = ['zomg'];
return options.fn(this, {blockParams: blockParams}); return options.fn(this, { blockParams: blockParams });
}, },
wycats: function(passiveVoice, noun, blah, options) { wycats: function(passiveVoice, noun, blah, options) {
equal(options.ids[0], 'zomg.a'); equal(options.ids[0], 'zomg.a');
equal(options.ids[1], 'slave.driver'); equal(options.ids[1], 'slave.driver');
equal(options.ids[2], 'zomg'); 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() { describe('builtin helpers', function() {
@@ -140,53 +229,119 @@ describe('track ids', function() {
describe('#each', function() { describe('#each', function() {
it('should track contextPath for arrays', 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() { 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() { 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() { 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() { describe('#with', function() {
it('should track contextPath', 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() { 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() { describe('#blockHelperMissing', function() {
it('should track contextPath for arrays', 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() { 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() { 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() { it('should pass track id for basic partial', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude}}{{/dudes}}', {trackIds: true}), var template = CompilerContext.compile(
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}',
{ trackIds: true }
var partials = { ),
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true}) hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
}; };
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n'); var partials = {
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n'
);
}); });
it('should pass track id for context partial', function() { it('should pass track id for context partial', function() {
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {trackIds: true}), var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; trackIds: true
}),
var partials = { hash = {
dude: CompilerContext.compile('{{#each this}}{{wycats name}}{{/each}}', {trackIds: true}) dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
}; };
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n'); var partials = {
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'
);
}); });
it('should invalidate context for partials with parameters', function() { it('should invalidate context for partials with parameters', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}', {trackIds: true}), var template = CompilerContext.compile(
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]}; 'Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}',
{ trackIds: true }
var partials = { ),
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true}) hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
}; };
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:true\nAlan:true\n'); var partials = {
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
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)) { if (!(safe instanceof Handlebars.SafeString)) {
throw new Error('Must be instance of 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() { it('it should not escape SafeString properties', function() {
var name = new Handlebars.SafeString('<em>Sean O&#x27;Malley</em>'); 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() { describe('#escapeExpression', function() {
it('shouhld escape html', 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;'); equals(Handlebars.Utils.escapeExpression('foo='), 'foo&#x3D;');
}); });
it('should not escape SafeString', function() { it('should not escape SafeString', function() {
@@ -58,7 +69,7 @@ describe('utils', function() {
equals(Handlebars.Utils.isEmpty(0), false); equals(Handlebars.Utils.isEmpty(0), false);
equals(Handlebars.Utils.isEmpty([1]), false); equals(Handlebars.Utils.isEmpty([1]), false);
equals(Handlebars.Utils.isEmpty('foo'), 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; A.prototype.b = 4;
var b = {b: 2}; var b = { b: 2 };
Handlebars.Utils.extend(b, new A()); Handlebars.Utils.extend(b, new A());
+32 -10
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 // Simply run the thing and make sure it does not fail and that all of the
// stub methods are executed // stub methods are executed
var visitor = new Handlebars.Visitor(); 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 }} {{/bar}}')); visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{* bar }}')); visitor.accept(Handlebars.parse('{{* bar }}'));
@@ -40,7 +44,11 @@ describe('Visitor', function() {
equal(comment.value, 'comment'); 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() { describe('mutating', function() {
@@ -50,12 +58,15 @@ describe('Visitor', function() {
visitor.mutating = true; visitor.mutating = true;
visitor.StringLiteral = function(string) { 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"}}'); var ast = Handlebars.parse('{{foo foo="foo"}}');
visitor.accept(ast); 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() { it('should treat undefined resonse as identity', function() {
var visitor = new Handlebars.Visitor(); var visitor = new Handlebars.Visitor();
@@ -63,7 +74,10 @@ describe('Visitor', function() {
var ast = Handlebars.parse('{{foo foo=42}}'); var ast = Handlebars.parse('{{foo foo=42}}');
visitor.accept(ast); 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() { it('should remove false responses', function() {
var visitor = new Handlebars.Visitor(); var visitor = new Handlebars.Visitor();
@@ -78,7 +92,8 @@ describe('Visitor', function() {
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n'); equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
}); });
it('should throw when removing required values', function() { it('should throw when removing required values', function() {
shouldThrow(function() { shouldThrow(
function() {
var visitor = new Handlebars.Visitor(); var visitor = new Handlebars.Visitor();
visitor.mutating = true; visitor.mutating = true;
@@ -88,10 +103,14 @@ describe('Visitor', function() {
var ast = Handlebars.parse('{{foo 42}}'); var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast); visitor.accept(ast);
}, Handlebars.Exception, 'MustacheStatement requires path'); },
Handlebars.Exception,
'MustacheStatement requires path'
);
}); });
it('should throw when returning non-node responses', function() { it('should throw when returning non-node responses', function() {
shouldThrow(function() { shouldThrow(
function() {
var visitor = new Handlebars.Visitor(); var visitor = new Handlebars.Visitor();
visitor.mutating = true; visitor.mutating = true;
@@ -101,7 +120,10 @@ describe('Visitor', function() {
var ast = Handlebars.parse('{{foo 42}}'); var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast); visitor.accept(ast);
}, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting path on MustacheStatement'); },
Handlebars.Exception,
'Unexpected node type "undefined" found when accepting path on MustacheStatement'
);
}); });
}); });
describe('arrays', function() { describe('arrays', function() {
@@ -110,7 +132,7 @@ describe('Visitor', function() {
visitor.mutating = true; visitor.mutating = true;
visitor.StringLiteral = function(string) { 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"}}'); var ast = Handlebars.parse('{{foo "foo"}}');
+64 -15
View File
@@ -1,6 +1,6 @@
describe('whitespace control', function() { describe('whitespace control', function() {
it('should strip whitespace around mustache calls', 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;');
shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; '); shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
@@ -14,15 +14,23 @@ describe('whitespace control', function() {
describe('blocks', function() { describe('blocks', function() {
it('should strip whitespace around simple block calls', 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(' {{~#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(
shouldCompileTo(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', hash, ' abara '); ' \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() { it('should strip whitespace around inverse block calls', function() {
var hash = {}; 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(' {{^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() { 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');
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('{{#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(
shouldCompileTo('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar<'); '\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 = {}; hash = {};
@@ -56,21 +76,50 @@ describe('whitespace control', function() {
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz'); 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() { it('should strip whitespace around partials', function() {
shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar'); shouldCompileToWithPartials(
shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar'); 'foo {{~> dude~}} ',
shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar '); [{}, {}, { 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(
shouldCompileToWithPartials('foo\n {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo\n bar'); 'foo\n {{~> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo\n {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo\n bar'
);
}); });
it('should only strip whitespace once', function() { it('should only strip whitespace once', function() {
var hash = {foo: 'bar'}; var hash = { foo: 'bar' };
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar '); shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
}); });
+5 -2
View File
@@ -8,7 +8,9 @@ module.exports = function(grunt) {
execName = grunt.option('name'), execName = grunt.option('name'),
events = {}; events = {};
async.each(_.keys(metrics), function(name, complete) { async.each(
_.keys(metrics),
function(name, complete) {
if (/^_/.test(name) || (execName && name !== execName)) { if (/^_/.test(name) || (execName && name !== execName)) {
return complete(); return complete();
} }
@@ -18,6 +20,7 @@ module.exports = function(grunt) {
complete(); complete();
}); });
}, },
done); done
);
}); });
}; };
+12 -2
View File
@@ -10,7 +10,11 @@ module.exports = function(grunt) {
cmd = 'node_modules\\.bin\\jison.cmd'; cmd = 'node_modules\\.bin\\jison.cmd';
} }
var child = childProcess.spawn(cmd, ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); var child = childProcess.spawn(
cmd,
['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'],
{ stdio: 'inherit' }
);
child.on('exit', function(code) { child.on('exit', function(code) {
if (code != 0) { if (code != 0) {
grunt.fatal('Jison failure: ' + code); grunt.fatal('Jison failure: ' + code);
@@ -18,7 +22,13 @@ module.exports = function(grunt) {
return; return;
} }
var src = ['src/parser-prefix.js', 'handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join(''); var src = [
'src/parser-prefix.js',
'handlebars.js',
'src/parser-suffix.js'
]
.map(grunt.file.read)
.join('');
grunt.file.delete('handlebars.js'); grunt.file.delete('handlebars.js');
grunt.file.write('lib/handlebars/compiler/parser.js', src); grunt.file.write('lib/handlebars/compiler/parser.js', src);
+21 -6
View File
@@ -60,14 +60,20 @@ module.exports = function(grunt) {
throw new Error('Missing S3 config values'); throw new Error('Missing S3 config values');
} }
AWS.config.update({accessKeyId: key, secretAccessKey: secret}); AWS.config.update({ accessKeyId: key, secretAccessKey: secret });
} }
function publish(files, callback) { function publish(files, callback) {
var s3 = new AWS.S3(), var s3 = new AWS.S3(),
bucket = process.env.S3_BUCKET_NAME; bucket = process.env.S3_BUCKET_NAME;
async.each(_.keys(files), function(file, callback) { async.each(
var params = {Bucket: bucket, Key: file, Body: grunt.file.read(files[file])}; _.keys(files),
function(file, callback) {
var params = {
Bucket: bucket,
Key: file,
Body: grunt.file.read(files[file])
};
s3.putObject(params, function(err) { s3.putObject(params, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -77,15 +83,24 @@ module.exports = function(grunt) {
} }
}); });
}, },
callback); callback
);
} }
function fileMap(suffixes) { function fileMap(suffixes) {
var map = {}; var map = {};
_.each(['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'], function(file) { _.each(
[
'handlebars.js',
'handlebars.min.js',
'handlebars.runtime.js',
'handlebars.runtime.min.js'
],
function(file) {
_.each(suffixes, function(suffix) { _.each(suffixes, function(suffix) {
map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file; map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file;
}); });
}); }
);
return map; return map;
} }
}; };
+37 -7
View File
@@ -7,7 +7,7 @@ module.exports = function(grunt) {
var done = this.async(); var done = this.async();
var cmd = './bin/handlebars'; var cmd = './bin/handlebars';
var args = [ '-a', 'spec/artifacts/empty.handlebars' ]; var args = ['-a', 'spec/artifacts/empty.handlebars'];
// On Windows, the executable handlebars.js file cannot be run directly // On Windows, the executable handlebars.js file cannot be run directly
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
@@ -19,10 +19,19 @@ module.exports = function(grunt) {
throw err; throw err;
} }
var expected = fs.readFileSync('./spec/expected/empty.amd.js').toString().replace(/\r\n/g, '\n'); var expected = fs
.readFileSync('./spec/expected/empty.amd.js')
.toString()
.replace(/\r\n/g, '\n');
if (stdout.toString() !== expected) { if (stdout.toString() !== expected) {
throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"'); throw new Error(
'Expected binary output differed:\n\n"' +
stdout +
'"\n\n"' +
expected +
'"'
);
} }
done(); done();
@@ -31,7 +40,9 @@ module.exports = function(grunt) {
grunt.registerTask('test:mocha', function() { grunt.registerTask('test:mocha', function() {
var done = this.async(); var done = this.async();
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'}); var runner = childProcess.fork('./spec/env/runner', [], {
stdio: 'inherit'
});
runner.on('close', function(code) { runner.on('close', function(code) {
if (code !== 0) { if (code !== 0) {
grunt.fatal(code + ' tests failed'); grunt.fatal(code + ' tests failed');
@@ -42,7 +53,11 @@ module.exports = function(grunt) {
grunt.registerTask('test:cov', function() { grunt.registerTask('test:cov', function() {
var done = this.async(); var done = this.async();
var runner = childProcess.fork('node_modules/istanbul/lib/cli.js', ['cover', '--source-map', '--', './spec/env/runner.js'], {stdio: 'inherit'}); var runner = childProcess.fork(
'node_modules/istanbul/lib/cli.js',
['cover', '--source-map', '--', './spec/env/runner.js'],
{ stdio: 'inherit' }
);
runner.on('close', function(code) { runner.on('close', function(code) {
if (code != 0) { if (code != 0) {
grunt.fatal(code + ' tests failed'); grunt.fatal(code + ' tests failed');
@@ -53,7 +68,9 @@ module.exports = function(grunt) {
grunt.registerTask('test:min', function() { grunt.registerTask('test:min', function() {
var done = this.async(); var done = this.async();
var runner = childProcess.fork('./spec/env/runner', ['--min'], {stdio: 'inherit'}); var runner = childProcess.fork('./spec/env/runner', ['--min'], {
stdio: 'inherit'
});
runner.on('close', function(code) { runner.on('close', function(code) {
if (code !== 0) { if (code !== 0) {
grunt.fatal(code + ' tests failed'); grunt.fatal(code + ' tests failed');
@@ -65,7 +82,20 @@ module.exports = function(grunt) {
grunt.registerTask('test:check-cov', function() { grunt.registerTask('test:check-cov', function() {
var done = this.async(); var done = this.async();
var runner = childProcess.fork('node_modules/istanbul/lib/cli.js', ['check-coverage', '--statements', '100', '--functions', '100', '--branches', '100', '--lines 100'], {stdio: 'inherit'}); var runner = childProcess.fork(
'node_modules/istanbul/lib/cli.js',
[
'check-coverage',
'--statements',
'100',
'--functions',
'100',
'--branches',
'100',
'--lines 100'
],
{ stdio: 'inherit' }
);
runner.on('close', function(code) { runner.on('close', function(code) {
if (code != 0) { if (code != 0) {
grunt.fatal('Coverage check failed: ' + code); grunt.fatal('Coverage check failed: ' + code);
+13 -5
View File
@@ -17,7 +17,10 @@ module.exports = {
}); });
}, },
clean: function(callback) { clean: function(callback) {
childProcess.exec('git diff-index --name-only HEAD --', {}, function(err, stdout) { childProcess.exec('git diff-index --name-only HEAD --', {}, function(
err,
stdout
) {
callback(undefined, !err && !stdout); callback(undefined, !err && !stdout);
}); });
}, },
@@ -47,10 +50,13 @@ module.exports = {
}); });
}, },
master: function(callback) { master: function(callback) {
childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) { childProcess.exec('git rev-parse --short origin/master', {}, function(
err,
stdout
) {
// This will error if master was not checked out but in this case we know we are not master // This will error if master was not checked out but in this case we know we are not master
// so we can ignore. // so we can ignore.
if (err && !(/Needed a single revision/.test(err.message))) { if (err && !/Needed a single revision/.test(err.message)) {
throw new Error('git.master: ' + err.message); throw new Error('git.master: ' + err.message);
} }
@@ -77,7 +83,9 @@ module.exports = {
}); });
}, },
tag: function(name, callback) { tag: function(name, callback) {
childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err) { childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(
err
) {
if (err) { if (err) {
throw new Error('git.tag: ' + err.message); throw new Error('git.tag: ' + err.message);
} }
@@ -98,7 +106,7 @@ module.exports = {
}); });
var versionTags = tags.filter(function(info) { var versionTags = tags.filter(function(info) {
return (/^v/.test(info[0])); return /^v/.test(info[0]);
}); });
callback(undefined, versionTags[0] || tags[0]); callback(undefined, versionTags[0] || tags[0]);
+35 -9
View File
@@ -3,13 +3,20 @@ var async = require('neo-async'),
semver = require('semver'); semver = require('semver');
module.exports = function(grunt) { module.exports = function(grunt) {
grunt.registerTask('version', 'Updates the current release version', function() { grunt.registerTask(
'version',
'Updates the current release version',
function() {
var done = this.async(), var done = this.async(),
pkg = grunt.config('pkg'), pkg = grunt.config('pkg'),
version = grunt.option('ver'); version = grunt.option('ver');
if (!semver.valid(version)) { if (!semver.valid(version)) {
throw new Error('Must provide a version number (Ex: --ver=1.0.0):\n\t' + version + '\n\n'); throw new Error(
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
version +
'\n\n'
);
} }
pkg.version = version; pkg.version = version;
@@ -17,11 +24,28 @@ module.exports = function(grunt) {
grunt.log.writeln('Updating to version ' + version); grunt.log.writeln('Updating to version ' + version);
async.each([ async.each(
['lib/handlebars/base.js', (/const VERSION = ['"](.*)['"];/), 'const VERSION = \'' + version + '\';'], [
['components/bower.json', (/"version":.*/), '"version": "' + version + '",'], [
['components/package.json', /"version":.*/, '"version": "' + version + '",'], 'lib/handlebars/base.js',
['components/handlebars.js.nuspec', (/<version>.*<\/version>/), '<version>' + version + '</version>'] /const VERSION = ['"](.*)['"];/,
"const VERSION = '" + version + "';"
],
[
'components/bower.json',
/"version":.*/,
'"version": "' + version + '",'
],
[
'components/package.json',
/"version":.*/,
'"version": "' + version + '",'
],
[
'components/handlebars.js.nuspec',
/<version>.*<\/version>/,
'<version>' + version + '</version>'
]
], ],
function(args, callback) { function(args, callback) {
replace.apply(undefined, args); replace.apply(undefined, args);
@@ -31,8 +55,10 @@ module.exports = function(grunt) {
function() { function() {
grunt.task.run(['default']); grunt.task.run(['default']);
done(); done();
}); }
}); );
}
);
function replace(path, regex, value) { function replace(path, regex, value) {
var content = grunt.file.read(path); var content = grunt.file.read(path);