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
+98 -48
View File
@@ -1,32 +1,42 @@
/* eslint-disable no-process-env */
module.exports = function(grunt) {
grunt.initConfig({
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: {
dist: {
options: {
processContent: function(content) {
return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
+ content;
return (
grunt.template.process(
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
) + content
);
}
},
files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
]
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
},
cdnjs: {
files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
]
},
components: {
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: {
modules: 'amd'
},
files: [{
expand: true,
cwd: 'lib/',
src: '**/!(index).js',
dest: 'dist/amd/'
}]
files: [
{
expand: true,
cwd: 'lib/',
src: '**/!(index).js',
dest: 'dist/amd/'
}
]
},
cjs: {
options: {
modules: 'common'
},
files: [{
cwd: 'lib/',
expand: true,
src: '**/!(index).js',
dest: 'dist/cjs/'
}]
files: [
{
cwd: 'lib/',
expand: true,
src: '**/!(index).js',
dest: 'dist/cjs/'
}
]
}
},
webpack: {
@@ -67,7 +81,12 @@ module.exports = function(grunt) {
module: {
loaders: [
// 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: {
@@ -116,15 +135,17 @@ module.exports = function(grunt) {
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
},
dist: {
files: [{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
files: [
{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
}
}
}]
]
}
},
@@ -148,28 +169,41 @@ module.exports = function(grunt) {
all: {
options: {
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,
concurrency: 4,
browsers: [
{browserName: 'chrome'},
{browserName: 'firefox', platform: 'Linux'},
{ browserName: 'chrome' },
{ browserName: 'firefox', platform: 'Linux' },
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
// {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: {
options: {
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,
concurrency: 2,
browsers: [
{browserName: 'chrome'}
]
browsers: [{ browserName: 'chrome' }]
}
}
},
@@ -180,7 +214,6 @@ module.exports = function(grunt) {
bg: false,
fail: true
}
},
watch: {
@@ -211,9 +244,9 @@ module.exports = function(grunt) {
grunt.task.loadTasks('tasks');
this.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'globals']
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'globals']
);
this.registerTask('node', ['babel:cjs']);
@@ -231,13 +264,30 @@ module.exports = function(grunt) {
this.registerTask('amd', ['babel:amd', 'requirejs']);
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
grunt.registerTask('extensive-tests-and-publish-to-aws', ['default', 'bgShell:integrationTests', 'sauce', 'metrics', 'publish:latest']);
grunt.registerTask('on-file-change', ['build', 'amd', 'concat:tests', 'test']);
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'bgShell:integrationTests',
'sauce',
'metrics',
'publish:latest'
]);
grunt.registerTask('on-file-change', [
'build',
'amd',
'concat:tests',
'test'
]);
// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('integration-tests', ['default', 'bgShell:integrationTests']);
grunt.registerTask('integration-tests', [
'default',
'bgShell:integrationTests'
]);
};
+11 -6
View File
@@ -1,12 +1,14 @@
var async = require('neo-async'),
fs = require('fs'),
zlib = require('zlib');
fs = require('fs'),
zlib = require('zlib');
module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'),
distSizes = {};
distSizes = {};
async.each(distFiles, function(file, callback) {
async.each(
distFiles,
function(file, callback) {
var content;
try {
content = fs.readFileSync('dist/' + file);
@@ -32,7 +34,10 @@ module.exports = function(grunt, callback) {
});
},
function() {
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
grunt.log.writeln(
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
);
callback([distSizes]);
});
}
);
};
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) {
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
return;
}
+9 -4
View File
@@ -1,5 +1,5 @@
var _ = require('underscore'),
templates = require('./templates');
templates = require('./templates');
module.exports = function(grunt, callback) {
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
@@ -8,12 +8,17 @@ module.exports = function(grunt, callback) {
var templateSizes = {};
_.each(templates, function(info, template) {
var src = info.handlebars,
compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {
knownHelpersOnly: true,
knownHelpers: info.helpers
});
templateSizes[template] = compiled.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]);
};
+2 -1
View File
@@ -8,5 +8,6 @@ module.exports = {
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 = {
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}}',
dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}',
+8 -1
View File
@@ -1,4 +1,11 @@
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}}'
};
+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
items: [
{name: 'red', current: true, url: '#Red'},
{name: 'green', current: false, url: '#Green'},
{name: 'blue', current: false, url: '#Blue'}
{ name: 'red', current: true, url: '#Red' },
{ name: 'green', current: false, url: '#Green' },
{ name: 'blue', current: false, url: '#Blue' }
]
},
+8 -1
View File
@@ -1,4 +1,11 @@
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}}'
};
+9 -1
View File
@@ -1,5 +1,13 @@
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}}',
mustache: '{{#names}}{{foo}}{{/names}}',
eco: '<% for item in @names: %><%= @foo %><% end %>'
+13 -3
View File
@@ -1,6 +1,16 @@
module.exports = {
context: { 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}}',
context: {
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}}',
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);
templates.forEach(function(template) {
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
return;
}
module.exports[RegExp.$1] = require('./' + RegExp.$1);
+4 -1
View File
@@ -1,5 +1,8 @@
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: {
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
+10 -2
View File
@@ -1,8 +1,16 @@
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: {
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}}',
+5 -3
View File
@@ -1,7 +1,9 @@
module.exports = {
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
context: { person: { name: { bar: { baz: 'Larry' } }, age: 45 } },
handlebars:
'{{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}}'
};
+1 -2
View File
@@ -1,8 +1,7 @@
module.exports = {
context: {name: 'Mick', count: 30},
context: { name: 'Mick', count: 30 },
handlebars: 'Hello {{name}}! You have {{count}} new messages.',
dust: 'Hello {name}! You have {count} new messages.',
mustache: 'Hello {{name}}! You have {{count}} new messages.',
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
};
+49 -25
View File
@@ -1,19 +1,27 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),
eco, dust, Handlebars, Mustache;
runner = require('./util/template-runner'),
eco,
dust,
Handlebars,
Mustache;
try {
dust = require('dustjs-linkedin');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}
try {
Mustache = require('mustache');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}
try {
eco = require('eco');
} catch (err) { /* NOP */ }
} catch (err) {
/* NOP */
}
function error() {
throw new Error('EWOT');
@@ -22,21 +30,28 @@ function error() {
function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name,
context = template.context,
partials = template.partials,
handlebarsOut,
compatOut,
dustOut,
ecoOut,
mustacheOut;
context = template.context,
partials = template.partials,
handlebarsOut,
compatOut,
dustOut,
ecoOut,
mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function(partial, partialName) {
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
var handlebar = Handlebars.compile(template.handlebars, { data: false }),
compat = Handlebars.compile(template.handlebars, {
data: false,
compat: true
}),
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);
@@ -58,7 +73,9 @@ function makeSuite(bench, name, template, handlebarsOnly) {
dustOut = false;
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() {
dust.render(templateName, context, function() {});
@@ -84,7 +101,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
if (Mustache) {
var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache;
mustachePartials = partials && partials.mustache;
if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
@@ -107,9 +124,16 @@ function makeSuite(bench, name, template, handlebarsOnly) {
b = b.replace(/\s/g, '');
if (handlebarsOut !== b) {
throw new Error('Template output mismatch: ' + name
+ '\n\nHandlebars: ' + handlebarsOut
+ '\n\n' + lang + ': ' + b);
throw new Error(
'Template output mismatch: ' +
name +
'\n\nHandlebars: ' +
handlebarsOut +
'\n\n' +
lang +
': ' +
b
);
}
}
+47 -20
View File
@@ -1,5 +1,5 @@
var _ = require('underscore'),
Benchmark = require('benchmark');
Benchmark = require('benchmark');
function BenchWarmer() {
this.benchmarks = [];
@@ -33,16 +33,21 @@ BenchWarmer.prototype = {
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;
var bench = new Benchmark(fn, {
name: this.suiteName + ': ' + name,
onComplete: function() {
if (first) { self.startLine(suiteName); }
if (first) {
self.startLine(suiteName);
}
self.writeBench(bench);
self.currentBenches.push(bench);
}, onError: function() {
},
onError: function() {
self.errors[this.name] = this;
}
});
@@ -75,10 +80,14 @@ BenchWarmer.prototype = {
});
print('\n');
var errors = false, prop, bench;
var errors = false,
prop,
bench;
for (prop in self.errors) {
if (Object.prototype.hasOwnProperty.call(self, prop)
&& self.errors[prop].error.message !== 'EWOT') {
if (
Object.prototype.hasOwnProperty.call(self, prop) &&
self.errors[prop].error.message !== 'EWOT'
) {
errors = true;
break;
}
@@ -107,23 +116,39 @@ BenchWarmer.prototype = {
},
scaleTimes: function() {
var scaled = this.scaled = {};
_.each(this.times, function(times, name) {
var output = scaled[name] = {};
var scaled = (this.scaled = {});
_.each(
this.times,
function(times, name) {
var output = (scaled[name] = {});
_.each(times, function(time, lang) {
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
}, this);
}, this);
_.each(
times,
function(time, lang) {
output[lang] = (
((time - this.minimum) / (this.maximum - this.minimum)) *
100
).toFixed(2);
},
this
);
},
this
);
},
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++) {
var name = names[i];
if (benchSize < name.length) { benchSize = name.length; }
if (benchSize < name.length) {
benchSize = name.length;
}
}
this.nameSize = benchSize + 2;
@@ -146,7 +171,9 @@ BenchWarmer.prototype = {
},
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];
});
@@ -164,9 +191,9 @@ BenchWarmer.prototype = {
if (!bench.error) {
var count = bench.hz,
moe = count * bench.stats.rme / 100,
minimum,
maximum;
moe = (count * bench.stats.rme) / 100,
minimum,
maximum;
count = Math.round(count / 1000);
moe = Math.round(moe / 1000);
+3 -3
View File
@@ -1,12 +1,12 @@
var _ = require('underscore'),
BenchWarmer = require('./benchwarmer'),
templates = require('../templates');
BenchWarmer = require('./benchwarmer'),
templates = require('../templates');
module.exports = function(grunt, makeSuite, callback) {
var warmer = new BenchWarmer();
var handlebarsOnly = grunt.option('handlebars-only'),
grep = grunt.option('grep');
grep = grunt.option('grep');
if (grep) {
grep = new RegExp(grep);
}
@@ -5,7 +5,7 @@ var Handlebars = require('handlebars');
console.log('Testing built Handlebars with Node version ' + process.version);
var template = Handlebars.compile('Author: {{author}}');
var output = template({author: 'Yehuda'});
var output = template({ author: 'Yehuda' });
assert.strictEqual(output, 'Author: Yehuda');
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 {assertEquals} from "../../webpack-test/src/lib/assert";
import { assertEquals } from '../../webpack-test/src/lib/assert';
Handlebars.registerHelper('loud', function(text) {
return text.toUpperCase();
return text.toUpperCase();
});
const template = hbs`{{loud name}}`;
const output = template({name: 'yehuda'})
const output = template({ name: 'yehuda' });
assertEquals(output, 'YEHUDA')
assertEquals(output, 'YEHUDA');
@@ -1,5 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
@@ -3,30 +3,30 @@ const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: false },
}
}
]
},
optimization: {
minimize: false
}
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: false }
}
}
]
},
optimization: {
minimize: false
}
};
@@ -1,6 +1,6 @@
import Handlebars from 'handlebars/dist/handlebars';
import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';
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 {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';
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';
assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda');
assertEquals(testTemplate({ author: 'Yehuda' }).trim(), 'Author: Yehuda');
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';
const HandlebarsViaRequire = require('handlebars');
import {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';
HandlebarsViaImport.registerHelper('loud', function(text) {
return text.toUpperCase();
return text.toUpperCase();
});
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 {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';
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 {assertEquals} from './lib/assert';
import { assertEquals } from './lib/assert';
const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
@@ -1,5 +1,5 @@
export function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
if (actual !== expected) {
throw new Error(`Expected "${actual}" to equal "${expected}"`);
}
}
@@ -3,20 +3,18 @@ const fs = require('fs');
const testFiles = fs.readdirSync('src');
const entryPoints = {};
testFiles
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
.filter(file => file.match(/-test.js$/))
.forEach(file => {
entryPoints[file] = `./src/${file}`;
});
module.exports = {
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [
{test: /\.handlebars$/, loader: 'handlebars-loader'}
]
}
entry: entryPoints,
output: {
filename: '[name]',
path: __dirname + '/dist'
},
module: {
rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }]
}
};
+5 -1
View File
@@ -2,7 +2,11 @@ import runtime from './handlebars.runtime';
// Compiler imports
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 JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
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 {registerDefaultHelpers} from './helpers';
import {registerDefaultDecorators} from './decorators';
import { registerDefaultHelpers } from './helpers';
import { registerDefaultDecorators } from './decorators';
import logger from './logger';
export const VERSION = '4.5.3';
@@ -38,7 +38,9 @@ HandlebarsEnvironment.prototype = {
registerHelper: function(name, fn) {
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);
} else {
this.helpers[name] = fn;
@@ -53,7 +55,9 @@ HandlebarsEnvironment.prototype = {
extend(this.partials, name);
} else {
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;
}
@@ -64,7 +68,9 @@ HandlebarsEnvironment.prototype = {
registerDecorator: function(name, fn) {
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);
} else {
this.decorators[name] = fn;
@@ -77,4 +83,4 @@ HandlebarsEnvironment.prototype = {
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 has at least one parameter or hash segment
helperExpression: function(node) {
return (node.type === 'SubExpression')
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
&& !!((node.params && node.params.length) || node.hash));
return (
node.type === 'SubExpression' ||
((node.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
},
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
// `..` or `this`.
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 modify the object to operate properly.
export default AST;
+3 -1
View File
@@ -10,7 +10,9 @@ extend(yy, Helpers);
export function parseWithoutProcessing(input, options) {
// 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;
+21 -16
View File
@@ -1,5 +1,5 @@
/* global define */
import {isArray} from '../utils';
import { isArray } from '../utils';
let SourceNode;
@@ -38,7 +38,7 @@ if (!SourceNode) {
this.src = chunks + this.src;
},
toStringWithSourceMap: function() {
return {code: this.toString()};
return { code: this.toString() };
},
toString: function() {
return this.src;
@@ -46,7 +46,6 @@ if (!SourceNode) {
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
@@ -62,7 +61,6 @@ function castChunk(chunk, codeGen, loc) {
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
@@ -94,17 +92,22 @@ CodeGen.prototype = {
},
empty: function() {
let loc = this.currentLocation || {start: {}};
let loc = this.currentLocation || { start: {} };
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) {
return chunk;
}
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) {
@@ -113,13 +116,17 @@ CodeGen.prototype = {
},
quotedString: function(str) {
return '"' + (str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
return (
'"' +
(str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') +
'"'
);
},
objectLiteral: function(obj) {
@@ -138,7 +145,6 @@ CodeGen.prototype = {
return ret;
},
generateList: function(entries) {
let ret = this.empty();
@@ -163,4 +169,3 @@ CodeGen.prototype = {
};
export default CodeGen;
+97 -51
View File
@@ -1,7 +1,7 @@
/* eslint-disable new-cap */
import Exception from '../exception';
import {isArray, indexOf, extend} from '../utils';
import { isArray, indexOf, extend } from '../utils';
import AST from './ast';
const slice = [].slice;
@@ -24,8 +24,11 @@ Compiler.prototype = {
for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
otherOpcode = other.opcodes[i];
if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false;
}
}
@@ -54,24 +57,28 @@ Compiler.prototype = {
options.blockParams = options.blockParams || [];
options.knownHelpers = extend(Object.create(null), {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
'if': true,
'unless': true,
'with': true,
'log': true,
'lookup': true
}, options.knownHelpers);
options.knownHelpers = extend(
Object.create(null),
{
helperMissing: true,
blockHelperMissing: true,
each: true,
if: true,
unless: true,
with: true,
log: true,
lookup: true
},
options.knownHelpers
);
return this.accept(program);
},
compileProgram: function(program) {
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
result = childCompiler.compile(program, this.options),
guid = this.guid++;
result = childCompiler.compile(program, this.options),
guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
@@ -97,7 +104,7 @@ Compiler.prototype = {
this.options.blockParams.unshift(program.blockParams);
let body = program.body,
bodyLength = body.length;
bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]);
}
@@ -114,7 +121,7 @@ Compiler.prototype = {
transformLiteralToPath(block);
let program = block.program,
inverse = block.inverse;
inverse = block.inverse;
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
@@ -149,7 +156,7 @@ Compiler.prototype = {
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
path = decorator.path;
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
@@ -165,17 +172,20 @@ Compiler.prototype = {
let params = partial.params;
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) {
if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined');
} else {
params.push({type: 'PathExpression', parts: [], depth: 0});
params.push({ type: 'PathExpression', parts: [], depth: 0 });
}
}
let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
@@ -208,7 +218,6 @@ Compiler.prototype = {
this.DecoratorBlock(decorator);
},
ContentStatement: function(content) {
if (content.value) {
this.opcode('appendContent', content.value);
@@ -231,8 +240,8 @@ Compiler.prototype = {
},
ambiguousSexpr: function(sexpr, program, inverse) {
let path = sexpr.path,
name = path.parts[0],
isBlock = program != null || inverse != null;
name = path.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', path.depth);
@@ -254,19 +263,27 @@ Compiler.prototype = {
helperSexpr: function(sexpr, program, inverse) {
let params = this.setupFullMustacheParams(sexpr, program, inverse),
path = sexpr.path,
name = path.parts[0];
path = sexpr.path,
name = path.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} 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 {
path.strict = true;
path.falsy = true;
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)
);
}
},
@@ -275,8 +292,8 @@ Compiler.prototype = {
this.opcode('getContext', path.depth);
let name = path.parts[0],
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
@@ -287,7 +304,13 @@ Compiler.prototype = {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
}
},
@@ -313,8 +336,8 @@ Compiler.prototype = {
Hash: function(hash) {
let pairs = hash.pairs,
i = 0,
l = pairs.length;
i = 0,
l = pairs.length;
this.opcode('pushHash');
@@ -329,7 +352,11 @@ Compiler.prototype = {
// HELPERS
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) {
@@ -358,7 +385,7 @@ Compiler.prototype = {
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
let name = sexpr.path.parts[0],
options = this.options;
options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
@@ -386,9 +413,7 @@ Compiler.prototype = {
if (this.stringParams) {
if (value.replace) {
value = value
.replace(/^(\.?\.\/)*/g, '')
.replace(/\//g, '.');
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
}
if (val.depth) {
@@ -406,7 +431,7 @@ Compiler.prototype = {
if (this.trackIds) {
let blockParamIndex;
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
blockParamIndex = this.blockParamIndex(val.parts[0]);
blockParamIndex = this.blockParamIndex(val.parts[0]);
}
if (blockParamIndex) {
let blockParamChild = val.parts.slice(1).join('.');
@@ -415,9 +440,9 @@ Compiler.prototype = {
value = val.original || value;
if (value.replace) {
value = value
.replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '')
.replace(/^\.$/, '');
.replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '')
.replace(/^\.$/, '');
}
this.opcode('pushId', val.type, value);
@@ -444,9 +469,13 @@ Compiler.prototype = {
},
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],
param = blockParams && indexOf(blockParams, name);
param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) {
return [depth, param];
}
@@ -455,8 +484,14 @@ Compiler.prototype = {
};
export function precompile(input, options, env) {
if (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);
if (
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 || {};
@@ -468,13 +503,19 @@ export function precompile(input, options, env) {
}
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options);
environment = new env.Compiler().compile(ast, options);
return new env.JavaScriptCompiler().compile(environment, options);
}
export function compile(input, options = {}, env) {
if (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);
if (
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);
@@ -489,8 +530,13 @@ export function compile(input, options = {}, env) {
function compileInput() {
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(
environment,
options,
undefined,
true
);
return env.template(templateSpec);
}
+29 -20
View File
@@ -4,9 +4,12 @@ function validateClose(open, close) {
close = close.path ? close.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,27 +41,26 @@ export function stripFlags(open, close) {
}
export function stripComment(comment) {
return comment.replace(/^\{\{~?!-?-?/, '')
.replace(/-?-?~?\}\}$/, '');
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
}
export function preparePath(data, parts, loc) {
loc = this.locInfo(loc);
let original = data ? '@' : '',
dig = [],
depth = 0;
dig = [],
depth = 0;
for (let i = 0, l = parts.length; i < l; i++) {
let part = parts[i].part,
// If we have [] syntax then we do not treat path references as operators,
// i.e. foo.[this] resolves to approximately context.foo['this']
isLiteral = parts[i].original !== part;
// If we have [] syntax then we do not treat path references as operators,
// i.e. foo.[this] resolves to approximately context.foo['this']
isLiteral = parts[i].original !== part;
original += (parts[i].separator || '') + part;
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
if (dig.length > 0) {
throw new Exception('Invalid path: ' + original, {loc});
throw new Exception('Invalid path: ' + original, { loc });
} else if (part === '..') {
depth++;
}
@@ -80,9 +82,9 @@ export function preparePath(data, parts, loc) {
export function prepareMustache(path, params, hash, open, strip, locInfo) {
// Must use charAt to support IE pre-10
let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = (/\*/.test(open));
let decorator = /\*/.test(open);
return {
type: decorator ? 'Decorator' : 'MustacheStatement',
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) {
validateClose(openBlock, close);
}
let decorator = (/\*/.test(openBlock.open));
let decorator = /\*/.test(openBlock.open);
program.blockParams = openBlock.blockParams;
let inverse,
inverseStrip;
let inverse, inverseStrip;
if (inverseAndProgram) {
if (decorator) {
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
throw new Exception(
'Unexpected inverse block on decorator',
inverseAndProgram
);
}
if (inverseAndProgram.chain) {
@@ -166,7 +177,7 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
export function prepareProgram(statements, loc) {
if (!loc && statements.length) {
const firstLoc = statements[0].loc,
lastLoc = statements[statements.length - 1].loc;
lastLoc = statements[statements.length - 1].loc;
/* istanbul ignore else */
if (firstLoc && lastLoc) {
@@ -192,7 +203,6 @@ export function prepareProgram(statements, loc) {
};
}
export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close);
@@ -207,4 +217,3 @@ export function preparePartialBlock(open, program, close, locInfo) {
loc: this.locInfo(locInfo)
};
}
+208 -89
View File
@@ -1,8 +1,8 @@
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
import Exception from '../exception';
import {isArray} from '../utils';
import { isArray } from '../utils';
import CodeGen from './code-gen';
import {dangerousPropertyRegex} from '../helpers/lookup';
import { dangerousPropertyRegex } from '../helpers/lookup';
function Literal(value) {
this.value = value;
@@ -13,9 +13,16 @@ function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name/* , type*/) {
nameLookup: function(parent, name /* , type*/) {
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 _actualLookup();
@@ -34,7 +41,7 @@ JavaScriptCompiler.prototype = {
compilerInfo: function() {
const revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision];
versions = REVISION_CHANGES[revision];
return [revision, versions];
},
@@ -91,14 +98,18 @@ JavaScriptCompiler.prototype = {
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;
let opcodes = environment.opcodes,
opcode,
firstLoc,
i,
l;
opcode,
firstLoc,
i,
l;
for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i];
@@ -124,9 +135,20 @@ JavaScriptCompiler.prototype = {
this.decorators.push('return fn;');
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 {
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 = this.decorators.merge();
}
@@ -146,7 +168,7 @@ JavaScriptCompiler.prototype = {
ret.useDecorators = true;
}
let {programs, decorators} = this.context;
let { programs, decorators } = this.context;
for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) {
ret[i] = programs[i];
@@ -176,11 +198,11 @@ JavaScriptCompiler.prototype = {
if (!asObject) {
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);
if (options.srcName) {
ret = ret.toStringWithSourceMap({file: options.destName});
ret = ret.toStringWithSourceMap({ file: options.destName });
ret.map = ret.map && ret.map.toString();
} else {
ret = ret.toString();
@@ -221,7 +243,7 @@ JavaScriptCompiler.prototype = {
Object.keys(this.aliases).forEach(alias => {
let node = this.aliases[alias];
if (node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
node.children[0] = 'alias' + aliasCount;
}
});
@@ -243,18 +265,23 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params);
} else {
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
return this.source.wrap([
'function(',
params.join(','),
') {\n ',
source,
'}'
]);
}
},
mergeSource: function(varDeclarations) {
let isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer,
appendFirst,
sourceSeen,
bufferStart,
bufferEnd;
this.source.each((line) => {
appendOnly = !this.forceBuffer,
appendFirst,
sourceSeen,
bufferStart,
bufferEnd;
this.source.each(line => {
if (line.appendToBuffer) {
if (bufferStart) {
line.prepend(' + ');
@@ -280,7 +307,6 @@ JavaScriptCompiler.prototype = {
}
});
if (appendOnly) {
if (bufferStart) {
bufferStart.prepend('return ');
@@ -289,7 +315,8 @@ JavaScriptCompiler.prototype = {
this.source.push('return "";');
}
} else {
varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
varDeclarations +=
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
if (bufferStart) {
bufferStart.prepend('return buffer + ');
@@ -300,7 +327,9 @@ JavaScriptCompiler.prototype = {
}
if (varDeclarations) {
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
this.source.prepend(
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
);
}
return this.source.merge();
@@ -316,8 +345,10 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function(name) {
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params);
let blockName = this.popStack();
@@ -334,8 +365,10 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value
ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
let blockHelperMissing = this.aliasable(
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true);
this.flushInline();
@@ -344,9 +377,14 @@ JavaScriptCompiler.prototype = {
params.splice(1, 0, current);
this.pushSource([
'if (!', this.lastHelper, ') { ',
current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
'}']);
'if (!',
this.lastHelper,
') { ',
current,
' = ',
this.source.functionCall(blockHelperMissing, 'call', params),
'}'
]);
},
// [appendContent]
@@ -376,14 +414,24 @@ JavaScriptCompiler.prototype = {
// Otherwise, the empty string is appended
append: function() {
if (this.isInline()) {
this.replaceStack((current) => [' != null ? ', current, ' : ""']);
this.replaceStack(current => [' != null ? ', current, ' : ""']);
this.pushSource(this.appendToBuffer(this.popStack()));
} else {
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) {
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
appendEscaped: function() {
this.pushSource(this.appendToBuffer(
[this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
this.pushSource(
this.appendToBuffer([
this.aliasable('container.escapeExpression'),
'(',
this.popStack(),
')'
])
);
},
// [getContext]
@@ -480,7 +534,7 @@ JavaScriptCompiler.prototype = {
let len = parts.length;
for (; i < len; i++) {
/* eslint-disable no-loop-func */
this.replaceStack((current) => {
this.replaceStack(current => {
let lookup = this.nameLookup(current, parts[i], type);
// 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.
@@ -503,7 +557,14 @@ JavaScriptCompiler.prototype = {
// If the `value` is a lambda, replace it on the stack by
// the return value of the lambda
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]
@@ -543,7 +604,7 @@ JavaScriptCompiler.prototype = {
if (this.hash) {
this.hashes.push(this.hash);
}
this.hash = {values: {}, types: [], contexts: [], ids: []};
this.hash = { values: {}, types: [], contexts: [], ids: [] };
},
popHash: function() {
let hash = this.hash;
@@ -607,11 +668,16 @@ JavaScriptCompiler.prototype = {
// and inserts the decorator into the decorators list.
registerDecorator(paramSize, name) {
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
options = this.setupHelperArgs(name, paramSize);
options = this.setupHelperArgs(name, paramSize);
this.decorators.push([
'fn = ',
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
this.decorators.functionCall(foundDecorator, '', [
'fn',
'props',
'container',
options
]),
' || fn;'
]);
},
@@ -627,21 +693,32 @@ JavaScriptCompiler.prototype = {
// If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isSimple) {
let nonHelper = this.popStack(),
helper = this.setupHelper(paramSize, name);
helper = this.setupHelper(paramSize, name);
let possibleFunctionCalls = [];
if (isSimple) { // direct call to helper
if (isSimple) {
// direct call to helper
possibleFunctionCalls.push(helper.name);
}
// call a function from the input object
possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) {
possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
possibleFunctionCalls.push(
this.aliasable('container.hooks.helperMissing')
);
}
let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
let functionLookupCode = [
'(',
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
')'
];
let functionCall = this.source.functionCall(
functionLookupCode,
'call',
helper.callParams
);
this.push(functionCall);
},
@@ -685,22 +762,31 @@ JavaScriptCompiler.prototype = {
this.emptyHash();
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, ')'];
if (!this.options.strict) {
lookup[0] = '(helper = ';
lookup.push(
' != null ? helper : ',
this.aliasable('container.hooks.helperMissing')
' != null ? helper : ',
this.aliasable('container.hooks.helperMissing')
);
}
this.push([
'(', lookup,
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
'(typeof helper === ', this.aliasable('"function"'), ' ? ',
this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
'(',
lookup,
helper.paramsInit ? ['),(', helper.paramsInit] : [],
'),',
'(typeof helper === ',
this.aliasable('"function"'),
' ? ',
this.source.functionCall('helper', 'call', helper.callParams),
' : helper))'
]);
},
@@ -713,7 +799,7 @@ JavaScriptCompiler.prototype = {
// and pushes the result of the invocation back.
invokePartial: function(isDynamic, name, indent) {
let params = [],
options = this.setupParams(name, 1, params);
options = this.setupParams(name, 1, params);
if (isDynamic) {
name = this.popStack();
@@ -750,9 +836,9 @@ JavaScriptCompiler.prototype = {
// Pops a value off the stack and assigns it to the current hash
assignToHash: function(key) {
let value = this.popStack(),
context,
type,
id;
context,
type,
id;
if (this.trackIds) {
id = this.popStack();
@@ -778,8 +864,13 @@ JavaScriptCompiler.prototype = {
pushId: function(type, name, child) {
if (type === 'BlockParam') {
this.pushStackLiteral(
'blockParams[' + name[0] + '].path[' + name[1] + ']'
+ (child ? ' + ' + JSON.stringify('.' + child) : ''));
'blockParams[' +
name[0] +
'].path[' +
name[1] +
']' +
(child ? ' + ' + JSON.stringify('.' + child) : '')
);
} else if (type === 'PathExpression') {
this.pushString(name);
} else if (type === 'SubExpression') {
@@ -794,7 +885,9 @@ JavaScriptCompiler.prototype = {
compiler: JavaScriptCompiler,
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++) {
child = children[i];
@@ -807,7 +900,12 @@ JavaScriptCompiler.prototype = {
let index = this.context.programs.length;
child.index = 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.environments[index] = child;
@@ -835,7 +933,7 @@ JavaScriptCompiler.prototype = {
programExpression: function(guid) {
let child = this.environment.children[guid],
programParams = [child.index, 'data', child.blockParams];
programParams = [child.index, 'data', child.blockParams];
if (this.useBlockParams || this.useDepths) {
programParams.push('blockParams');
@@ -870,7 +968,11 @@ JavaScriptCompiler.prototype = {
pushSource: function(source) {
if (this.pendingContent) {
this.source.push(
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.appendToBuffer(
this.source.quotedString(this.pendingContent),
this.pendingLocation
)
);
this.pendingContent = undefined;
}
@@ -881,9 +983,9 @@ JavaScriptCompiler.prototype = {
replaceStack: function(callback) {
let prefix = ['('],
stack,
createdStack,
usedLiteral;
stack,
createdStack,
usedLiteral;
/* istanbul ignore next */
if (!this.isInline()) {
@@ -920,7 +1022,9 @@ JavaScriptCompiler.prototype = {
incrStack: function() {
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();
},
topStackName: function() {
@@ -947,9 +1051,9 @@ JavaScriptCompiler.prototype = {
popStack: function(wrapped) {
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;
} else {
if (!inline) {
@@ -964,8 +1068,8 @@ JavaScriptCompiler.prototype = {
},
topStack: function() {
let stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1];
let stack = this.isInline() ? this.inlineStack : this.compileStack,
item = stack[stack.length - 1];
/* istanbul ignore if */
if (item instanceof Literal) {
@@ -1007,9 +1111,13 @@ JavaScriptCompiler.prototype = {
setupHelper: function(paramSize, name, blockHelper) {
let params = [],
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
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 {
params: params,
@@ -1021,11 +1129,11 @@ JavaScriptCompiler.prototype = {
setupParams: function(helper, paramSize, params) {
let options = {},
contexts = [],
types = [],
ids = [],
objectArgs = !params,
param;
contexts = [],
types = [],
ids = [],
objectArgs = !params,
param;
if (objectArgs) {
params = [];
@@ -1043,7 +1151,7 @@ JavaScriptCompiler.prototype = {
}
let inverse = this.popStack(),
program = this.popStack();
program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)`
@@ -1106,7 +1214,6 @@ JavaScriptCompiler.prototype = {
}
};
(function() {
const reservedWords = (
'break else new var' +
@@ -1127,21 +1234,24 @@ JavaScriptCompiler.prototype = {
' null true false'
).split(' ');
const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {});
for (let i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true;
}
}());
})();
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) {
let stack = compiler.popStack(),
i = 0,
len = parts.length;
i = 0,
len = parts.length;
if (requireTerminal) {
len--;
}
@@ -1151,7 +1261,16 @@ function strictLookup(requireTerminal, compiler, parts, type) {
}
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 {
return stack;
}
+19 -12
View File
@@ -24,13 +24,14 @@ PrintVisitor.prototype.pad = function(string) {
PrintVisitor.prototype.Program = function(program) {
let out = '',
body = program.body,
i, l;
body = program.body,
i,
l;
if (program.blockParams) {
let blockParams = 'BLOCK PARAMS: [';
for (i = 0, l = program.blockParams.length; i < l; i++) {
blockParams += ' ' + program.blockParams[i];
blockParams += ' ' + program.blockParams[i];
}
blockParams += ' ]';
out += this.pad(blockParams);
@@ -52,11 +53,14 @@ PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.BlockStatement =
PrintVisitor.prototype.DecoratorBlock = function(block) {
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
block
) {
let out = '';
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
out += this.pad(
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++;
out += this.pad(this.SubExpression(block));
if (block.program) {
@@ -66,12 +70,16 @@ PrintVisitor.prototype.DecoratorBlock = function(block) {
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
if (block.program) {
this.padding++;
}
out += this.pad('{{^}}');
this.padding++;
out += this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
if (block.program) {
this.padding--;
}
}
this.padding--;
@@ -115,8 +123,8 @@ PrintVisitor.prototype.CommentStatement = function(comment) {
PrintVisitor.prototype.SubExpression = function(sexpr) {
let params = sexpr.params,
paramStrings = [],
hash;
paramStrings = [],
hash;
for (let i = 0, l = params.length; i < l; i++) {
paramStrings.push(this.accept(params[i]));
@@ -134,7 +142,6 @@ PrintVisitor.prototype.PathExpression = function(id) {
return (id.data ? '@' : '') + 'PATH:' + path;
};
PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"';
};
@@ -157,7 +164,7 @@ PrintVisitor.prototype.NullLiteral = function() {
PrintVisitor.prototype.Hash = function(hash) {
let pairs = hash.pairs,
joinedPairs = [];
joinedPairs = [];
for (let i = 0, l = pairs.length; i < l; i++) {
joinedPairs.push(this.accept(pairs[i]));
+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
// methods but will generally do the right thing without a lot of overhead.
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;
}
+47 -29
View File
@@ -14,18 +14,18 @@ WhitespaceControl.prototype.Program = function(program) {
let body = program.body;
for (let i = 0, l = body.length; i < l; i++) {
let current = body[i],
strip = this.accept(current);
strip = this.accept(current);
if (!strip) {
continue;
}
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace,
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace,
inlineStandalone =
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
if (strip.close) {
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 (current.type === 'PartialStatement') {
// 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,17 +62,17 @@ WhitespaceControl.prototype.Program = function(program) {
return program;
};
WhitespaceControl.prototype.BlockStatement =
WhitespaceControl.prototype.DecoratorBlock =
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
block
) {
this.accept(block.program);
this.accept(block.inverse);
// Find the inverse program that is involed with whitespace stripping.
let program = block.program || block.inverse,
inverse = block.program && block.inverse,
firstInverse = inverse,
lastInverse = inverse;
inverse = block.program && block.inverse,
firstInverse = inverse,
lastInverse = inverse;
if (inverse && inverse.chained) {
firstInverse = inverse.body[0].program;
@@ -112,9 +112,11 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
}
// Find standalone else statments
if (!this.options.ignoreStandalone
&& isPrevWhitespace(program.body)
&& isNextWhitespace(firstInverse.body)) {
if (
!this.options.ignoreStandalone &&
isPrevWhitespace(program.body) &&
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body);
omitRight(firstInverse.body);
}
@@ -125,13 +127,15 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
return strip;
};
WhitespaceControl.prototype.Decorator =
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
mustache
) {
return mustache.strip;
};
WhitespaceControl.prototype.PartialStatement =
WhitespaceControl.prototype.CommentStatement = function(node) {
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
node
) {
/* istanbul ignore next */
let strip = node.strip || {};
return {
@@ -141,7 +145,6 @@ WhitespaceControl.prototype.PartialStatement =
};
};
function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) {
i = body.length;
@@ -150,13 +153,15 @@ function isPrevWhitespace(body, i, isRoot) {
// Nodes that end with newlines are considered whitespace (but are special
// cased for strip operations)
let prev = body[i - 1],
sibling = body[i - 2];
sibling = body[i - 2];
if (!prev) {
return isRoot;
}
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) {
@@ -165,13 +170,15 @@ function isNextWhitespace(body, i, isRoot) {
}
let next = body[i + 1],
sibling = body[i + 2];
sibling = body[i + 2];
if (!next) {
return isRoot;
}
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.
function omitRight(body, i, multiple) {
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;
}
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;
}
@@ -202,13 +216,17 @@ function omitRight(body, i, multiple) {
// content is met.
function omitLeft(body, i, multiple) {
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;
}
// We omit the last node if it's whitespace only and not preceded by a non-content node.
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;
return current.leftStripped;
}
-1
View File
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import {extend} from '../utils';
import { extend } from '../utils';
export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) {
+14 -6
View File
@@ -1,12 +1,20 @@
const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
const errorProps = [
'description',
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];
function Exception(message, node) {
let loc = node && node.loc,
line,
endLineNumber,
column,
endColumn;
line,
endLineNumber,
column,
endColumn;
if (loc) {
line = loc.start.line;
@@ -1,9 +1,9 @@
import {appendContextPath, createFrame, isArray} from '../utils';
import { appendContextPath, createFrame, isArray } from '../utils';
export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) {
let inverse = options.inverse,
fn = options.fn;
fn = options.fn;
if (context === true) {
return fn(this);
@@ -22,8 +22,11 @@ export default function(instance) {
} else {
if (options.data && options.ids) {
let data = createFrame(options.data);
data.contextPath = appendContextPath(options.data.contextPath, options.name);
options = {data: data};
data.contextPath = appendContextPath(
options.data.contextPath,
options.name
);
options = { data: data };
}
return fn(context, options);
+26 -12
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';
export default function(instance) {
@@ -8,17 +14,20 @@ export default function(instance) {
}
let fn = options.fn,
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;
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) {
data = createFrame(options.data);
@@ -36,10 +45,15 @@ export default function(instance) {
}
}
ret = ret + fn(context[field], {
data: data,
blockParams: blockParams([context[field], field], [contextPath + field, null])
});
ret =
ret +
fn(context[field], {
data: data,
blockParams: blockParams(
[context[field], field],
[contextPath + field, null]
)
});
}
if (context && typeof context === 'object') {
+3 -1
View File
@@ -7,7 +7,9 @@ export default function(instance) {
return undefined;
} else {
// 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) {
instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');}
if (isFunction(conditional)) { conditional = conditional.call(this); }
if (arguments.length != 2) {
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.
// 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) {
if (arguments.length != 2) { throw new Exception('#unless requires exactly one argument');}
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
if (arguments.length != 2) {
throw new Exception('#unless requires exactly one argument');
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
});
}
+2 -2
View File
@@ -1,7 +1,7 @@
export default function(instance) {
instance.registerHelper('log', function(/* message, options */) {
let args = [undefined],
options = arguments[arguments.length - 1];
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
@@ -14,6 +14,6 @@ export default function(instance) {
}
args[0] = level;
instance.log(... args);
instance.log(...args);
});
}
+4 -1
View File
@@ -5,7 +5,10 @@ export default function(instance) {
if (!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 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';
export default function(instance) {
instance.registerHelper('with', function(context, options) {
if (arguments.length != 2) { throw new Exception('#with requires exactly one argument');}
if (isFunction(context)) { context = context.call(this); }
if (arguments.length != 2) {
throw new Exception('#with requires exactly one argument');
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn;
@@ -12,7 +22,10 @@ export default function(instance) {
let data = options.data;
if (options.data && options.ids) {
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, {
+7 -3
View File
@@ -1,4 +1,4 @@
import {indexOf} from './utils';
import { indexOf } from './utils';
let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
@@ -22,9 +22,13 @@ let logger = {
log: function(level, ...message) {
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];
if (!console[method]) { // eslint-disable-line no-console
// eslint-disable-next-line no-console
if (!console[method]) {
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
+1 -2
View File
@@ -1,8 +1,7 @@
/* global window */
export default function(Handlebars) {
/* istanbul ignore next */
let root = typeof global !== 'undefined' ? global : window,
$Handlebars = root.Handlebars;
$Handlebars = root.Handlebars;
/* istanbul ignore next */
Handlebars.noConflict = function() {
if (root.Handlebars === Handlebars) {
+147 -43
View File
@@ -1,30 +1,47 @@
import * as Utils from './utils';
import Exception from './exception';
import {COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES} from './base';
import {moveHelperToHooks} from './helpers';
import {
COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES
} from './base';
import { moveHelperToHooks } from './helpers';
export function checkRevision(compilerInfo) {
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= COMPILER_REVISION) {
if (
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return;
}
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception('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 + ').');
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception(
'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 {
// 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. ' +
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
throw new Exception(
'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) {
/* istanbul ignore next */
if (!env) {
throw new Exception('No environment passed to template');
@@ -40,7 +57,8 @@ export function template(templateSpec, env) {
env.VM.checkRevision(templateSpec.compiler);
// 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) {
if (options.hash) {
@@ -51,12 +69,21 @@ export function template(templateSpec, env) {
}
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) {
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);
}
if (result != null) {
@@ -73,7 +100,11 @@ export function template(templateSpec, env) {
}
return result;
} 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 = {
strict: function(obj, name, loc) {
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];
},
@@ -109,9 +142,17 @@ export function template(templateSpec, env) {
programs: [],
program: function(i, data, declaredBlockParams, blockParams, depths) {
let programWrapper = this.programs[i],
fn = this.fn(i);
fn = this.fn(i);
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) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
}
@@ -127,7 +168,7 @@ export function template(templateSpec, env) {
mergeIfNeeded: function(param, common) {
let obj = param || common;
if (param && common && (param !== common)) {
if (param && common && param !== common) {
obj = Utils.extend({}, common, param);
}
@@ -148,19 +189,40 @@ export function template(templateSpec, env) {
data = initData(context, data);
}
let depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
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 {
depths = [context];
}
}
function main(context/*, options*/) {
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
function main(context /*, options*/) {
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);
}
ret.isTop = true;
@@ -171,25 +233,32 @@ export function template(templateSpec, env) {
if (templateSpec.usePartial) {
// 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) {
container.decorators = Utils.extend({}, env.decorators, options.decorators);
container.decorators = Utils.extend(
{},
env.decorators,
options.decorators
);
}
container.hooks = {};
let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7;
let keepHelperInHelpers =
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else {
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
container.hooks = options.hooks;
}
};
ret._child = function(i, data, blockParams, depths) {
@@ -200,24 +269,47 @@ export function template(templateSpec, env) {
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;
}
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
export function wrapProgram(
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) {
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);
}
return fn(container,
context,
container.helpers, container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths);
return fn(
container,
context,
container.helpers,
container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths
);
}
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
@@ -259,8 +351,10 @@ export function invokePartial(partial, context, options) {
options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure
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
// i.e. the part inside the block of the partial call.
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) {
if (!data || !('root' in data)) {
@@ -296,7 +392,15 @@ function initData(context, data) {
function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) {
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);
}
return prog;
+17 -10
View File
@@ -1,4 +1,3 @@
const escape = {
'&': '&amp;',
'<': '&lt;',
@@ -10,13 +9,13 @@ const escape = {
};
const badChars = /[&<>"'`=]/g,
possible = /[&<>"'`=]/;
possible = /[&<>"'`=]/;
function escapeChar(chr) {
return escape[chr];
}
export function extend(obj/* , ...source */) {
export function extend(obj /* , ...source */) {
for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
@@ -40,16 +39,23 @@ let isFunction = function(value) {
/* istanbul ignore next */
if (isFunction(/x/)) {
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 */
/* istanbul ignore next */
export const isArray = Array.isArray || function(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
export const isArray =
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.
export function indexOf(array, value) {
@@ -61,7 +67,6 @@ export function indexOf(array, value) {
return -1;
}
export function escapeExpression(string) {
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
@@ -79,7 +84,9 @@ export function escapeExpression(string) {
string = '' + string;
}
if (!possible.test(string)) { return string; }
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
+117 -75
View File
@@ -2,9 +2,8 @@
import Async from 'neo-async';
import fs from 'fs';
import * as Handlebars from './handlebars';
import {basename} from 'path';
import {SourceMapConsumer, SourceNode} from 'source-map';
import { basename } from 'path';
import { SourceMapConsumer, SourceNode } from 'source-map';
module.exports.loadTemplates = function(opts, callback) {
loadStrings(opts, function(err, strings) {
@@ -25,14 +24,19 @@ module.exports.loadTemplates = function(opts, callback) {
function loadStrings(opts, callback) {
let strings = arrayCast(opts.string),
names = arrayCast(opts.name);
names = arrayCast(opts.name);
if (names.length !== strings.length
&& strings.length > 1) {
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
if (names.length !== strings.length && strings.length > 1) {
return callback(
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 !== '-') {
callback(undefined, string);
} else {
@@ -55,80 +59,94 @@ function loadStrings(opts, callback) {
source: string
}));
callback(err, strings);
});
}
);
}
function loadFiles(opts, callback) {
// 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 + '$');
let ret = [],
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
Async.whilst(() => queue.length, function(callback) {
let {template: path, root} = queue.shift();
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
Async.whilst(
() => queue.length,
function(callback) {
let { template: path, root } = queue.shift();
fs.stat(path, function(err, stat) {
if (err) {
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
}
fs.stat(path, function(err, stat) {
if (err) {
return callback(
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
}
if (stat.isDirectory()) {
opts.hasDirectory = true;
if (stat.isDirectory()) {
opts.hasDirectory = true;
fs.readdir(path, function(err, children) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
children.forEach(function(file) {
let childPath = path + '/' + file;
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
queue.push({template: childPath, root: root || path});
fs.readdir(path, function(err, children) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
});
children.forEach(function(file) {
let childPath = path + '/' + file;
callback();
});
if (
extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({ template: childPath, root: root || path });
}
});
callback();
});
} else {
fs.readFile(path, 'utf8', function(err, data) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data
});
callback();
});
}
});
},
function(err) {
if (err) {
callback(err);
} else {
fs.readFile(path, 'utf8', function(err, data) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data
});
callback();
});
callback(undefined, ret);
}
});
},
function(err) {
if (err) {
callback(err);
} else {
callback(undefined, ret);
}
});
);
}
module.exports.cli = function(opts) {
@@ -138,7 +156,9 @@ module.exports.cli = function(opts) {
}
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) {
@@ -147,12 +167,18 @@ module.exports.cli = function(opts) {
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
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.
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
&& !opts.templates[0].name) {
if (
!opts.amd &&
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true;
}
@@ -172,7 +198,11 @@ module.exports.cli = function(opts) {
let output = new SourceNode();
if (!opts.simple) {
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) {
output.add('var Handlebars = require("' + opts.commonjs + '");');
} 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 (opts.map) {
let consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
precompiled = SourceNode.fromStringWithSourceMap(
precompiled.code,
consumer
);
}
if (opts.simple) {
@@ -219,7 +252,14 @@ module.exports.cli = function(opts) {
if (opts.amd && !multiple) {
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;
}
// 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 require('uglify-js').minify(output.code, {
+158 -78
View File
@@ -7,44 +7,102 @@ describe('ast', function() {
describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
shouldThrow(
function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
},
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
});
});
describe('helpers', function() {
describe('#helperExpression', function() {
it('should handle mustache statements', function() {
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle block statements', function() {
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle subexpressions', function() {
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
});
it('should work with non-helper nodes', function() {
equals(AST.helpers.helperExpression({type: 'Program'}), false);
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
equals(
AST.helpers.helperExpression({ type: 'PartialStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'ContentStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'CommentStatement' }),
false
);
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
equals(
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
false
);
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
equals(AST.helpers.helperExpression({type: 'Hash'}), false);
equals(AST.helpers.helperExpression({type: 'HashPair'}), false);
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
});
});
});
@@ -61,17 +119,18 @@ describe('ast', function() {
/* eslint-disable no-multi-spaces */
ast = Handlebars.parse(
'line 1 {{line1Token}}\n' // 1
+ ' line 2 {{line2token}}\n' // 2
+ ' line 3 {{#blockHelperOnLine3}}\n' // 3
+ 'line 4{{line4token}}\n' // 4
+ 'line5{{else}}\n' // 5
+ '{{line6Token}}\n' // 6
+ '{{/blockHelperOnLine3}}\n' // 7
+ '{{#open}}\n' // 8
+ '{{else inverse}}\n' // 9
+ '{{else}}\n' // 10
+ '{{/open}}'); // 11
'line 1 {{line1Token}}\n' + // 1
' line 2 {{line2token}}\n' + // 2
' line 3 {{#blockHelperOnLine3}}\n' + // 3
'line 4{{line4token}}\n' + // 4
'line5{{else}}\n' + // 5
'{{line6Token}}\n' + // 6
'{{/blockHelperOnLine3}}\n' + // 7
'{{#open}}\n' + // 8
'{{else inverse}}\n' + // 9
'{{else}}\n' + // 10
'{{/open}}'
); // 11
/* eslint-enable no-multi-spaces */
body = ast.body;
@@ -92,35 +151,35 @@ describe('ast', function() {
it('gets MustacheStatement line numbers correct across newlines', function() {
var secondMustacheStatement = body[3];
testColumns(secondMustacheStatement, 2, 2, 8, 22);
});
});
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers the program of a block helper', function() {
var blockHelperNode = body[5],
program = blockHelperNode.program;
it('correctly records the line numbers the program of a block helper', function() {
var blockHelperNode = body[5],
program = blockHelperNode.program;
testColumns(program, 3, 5, 31, 5);
});
testColumns(program, 3, 5, 31, 5);
});
it('correctly records the line numbers of an inverse of a block helper', function() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
it('correctly records the line numbers of an inverse of a block helper', function() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 7, 13, 0);
});
testColumns(inverse, 5, 7, 13, 0);
});
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
});
describe('whitespace control', function() {
@@ -149,7 +208,9 @@ describe('ast', function() {
});
it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment~}} \nfoo\n {{~/comment}}');
var ast = Handlebars.parseWithoutProcessing(
' {{# comment~}} \nfoo\n {{~/comment}}'
);
equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
@@ -167,7 +228,9 @@ describe('ast', function() {
});
describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
var ast = Handlebars.parseWithoutProcessing(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, ' ');
@@ -178,13 +241,17 @@ describe('ast', function() {
equals(ast.body[2].value, ' ');
});
it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \nfoo\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n ');
});
it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \n{{foo}}\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \n');
@@ -192,7 +259,9 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n ');
});
it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
var ast = Handlebars.parseWithoutProcessing(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
@@ -202,8 +271,10 @@ describe('ast', function() {
equals(block.inverse.body[0].value, ' \n bar \n ');
});
it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parseWithoutProcessing(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
@@ -215,8 +286,10 @@ describe('ast', function() {
});
describe('blocks', function() {
it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parse(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, '');
@@ -227,22 +300,24 @@ describe('ast', function() {
});
it('marks initial block mustaches as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
block = ast.body[0];
block = ast.body[0];
equals(block.program.body[0].value, 'foo\n');
});
it('marks mustaches with children as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
block = ast.body[0];
block = ast.body[0];
equals(block.program.body[0].value, '');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n');
});
it('marks nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
var ast = Handlebars.parse(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, '');
@@ -252,9 +327,11 @@ describe('ast', function() {
equals(body[0].value, '');
});
it('does not mark nested block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
body = ast.body[0].program.body,
block = body[1];
var ast = Handlebars.parse(
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].omit, undefined);
@@ -264,9 +341,11 @@ describe('ast', function() {
equals(body[0].omit, undefined);
});
it('does not mark nested initial block mustaches as standalone', function() {
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
body = ast.body[0].program.body,
block = body[0];
var ast = Handlebars.parse(
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
),
body = ast.body[0].program.body,
block = body[0];
equals(block.program.body[0].value, ' \nfoo\n');
equals(block.inverse.body[0].value, ' bar \n ');
@@ -275,8 +354,10 @@ describe('ast', function() {
});
it('marks column 0 block mustaches as standalone', function() {
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
block = ast.body[1];
var ast = Handlebars.parse(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
@@ -348,4 +429,3 @@ describe('ast', function() {
});
});
});
+442 -139
View File
@@ -18,8 +18,12 @@ describe('basic context', function() {
});
it('compiling with a basic context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'It works if all the required keys are provided');
shouldCompileTo(
'Goodbye\n{{cruel}}\n{{world}}!',
{ cruel: 'cruel', world: 'world' },
'Goodbye\ncruel\nworld!',
'It works if all the required keys are provided'
);
});
it('compiling with a string context', function() {
@@ -27,15 +31,26 @@ describe('basic context', function() {
});
it('compiling with an undefined context', function() {
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
shouldCompileTo(
'Goodbye\n{{cruel}}\n{{world.bar}}!',
undefined,
'Goodbye\n\n!'
);
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
shouldCompileTo(
'{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}',
undefined,
'Goodbye'
);
});
it('comments', function() {
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'comments are ignored');
shouldCompileTo(
'{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
{ cruel: 'cruel', world: 'world' },
'Goodbye\ncruel\nworld!',
'comments are ignored'
);
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
@@ -47,237 +62,506 @@ describe('basic context', function() {
it('boolean', function() {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
'booleans show the contents when true');
shouldCompileTo(
string,
{ goodbye: true, world: 'world' },
'GOODBYE cruel world!',
'booleans show the contents when true'
);
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
'booleans do not show the contents when false');
shouldCompileTo(
string,
{ goodbye: false, world: 'world' },
'cruel world!',
'booleans do not show the contents when false'
);
});
it('zeros', function() {
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
'num1: 42, num2: 0');
shouldCompileTo(
'num1: {{num1}}, num2: {{num2}}',
{ num1: 42, num2: 0 },
'num1: 42, num2: 0'
);
shouldCompileTo('num: {{.}}', 0, 'num: 0');
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
shouldCompileTo('num: {{num1/num2}}', { num1: { num2: 0 } }, 'num: 0');
});
it('false', function() {
/* eslint-disable no-new-wrappers */
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
shouldCompileTo(
'val1: {{val1}}, val2: {{val2}}',
{ val1: false, val2: new Boolean(false) },
'val1: false, val2: false'
);
shouldCompileTo('val: {{.}}', false, 'val: false');
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
shouldCompileTo(
'val: {{val1/val2}}',
{ val1: { val2: false } },
'val: false'
);
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
shouldCompileTo(
'val1: {{{val1}}}, val2: {{{val2}}}',
{ val1: false, val2: new Boolean(false) },
'val1: false, val2: false'
);
shouldCompileTo(
'val: {{{val1/val2}}}',
{ val1: { val2: false } },
'val: false'
);
/* eslint-enable */
});
it('should handle undefined and null', function() {
shouldCompileTo('{{awesome undefined null}}',
{
awesome: function(_undefined, _null, options) {
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
}
},
'true true object');
shouldCompileTo('{{undefined}}',
{
'undefined': function() {
return 'undefined!';
}
},
'undefined!');
shouldCompileTo('{{null}}',
{
'null': function() {
return 'null!';
}
},
'null!');
shouldCompileTo(
'{{awesome undefined null}}',
{
awesome: function(_undefined, _null, options) {
return (
(_undefined === undefined) +
' ' +
(_null === null) +
' ' +
typeof options
);
}
},
'true true object'
);
shouldCompileTo(
'{{undefined}}',
{
undefined: function() {
return 'undefined!';
}
},
'undefined!'
);
shouldCompileTo(
'{{null}}',
{
null: function() {
return 'null!';
}
},
'null!'
);
});
it('newlines', function() {
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
});
it('escaping text', function() {
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
shouldCompileTo(
"Awesome's",
{},
"Awesome's",
"text is escaped so that it doesn't get caught on single quotes"
);
shouldCompileTo(
'Awesome\\',
{},
'Awesome\\',
"text is escaped so that the closing quote can't be ignored"
);
shouldCompileTo(
'Awesome\\\\ foo',
{},
'Awesome\\\\ foo',
"text is escaped so that it doesn't mess up backslashes"
);
shouldCompileTo(
'Awesome {{foo}}',
{ foo: '\\' },
'Awesome \\',
"text is escaped so that it doesn't mess up backslashes"
);
shouldCompileTo(
" ' ' ",
{},
" ' ' ",
'double quotes never produce invalid javascript'
);
});
it('escaping expressions', function() {
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with 3 handlebars aren't escaped");
shouldCompileTo(
'{{{awesome}}}',
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with 3 handlebars aren't escaped"
);
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
"expressions with {{& handlebars aren't escaped");
shouldCompileTo(
'{{&awesome}}',
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with {{& handlebars aren't escaped"
);
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped');
shouldCompileTo(
'{{awesome}}',
{ awesome: '&"\'`\\<>' },
'&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped'
);
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: &lt;b&gt;'}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'escaping should properly handle amperstands');
shouldCompileTo(
'{{awesome}}',
{ awesome: 'Escaped, <b> looks like: &lt;b&gt;' },
'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'escaping should properly handle amperstands'
);
});
it("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
"functions returning safestrings aren't escaped");
var hash = {
awesome: function() {
return new Handlebars.SafeString("&'\\<>");
}
};
shouldCompileTo(
'{{awesome}}',
hash,
"&'\\<>",
"functions returning safestrings aren't escaped"
);
});
it('functions', function() {
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
'functions are called and render their output');
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
'functions are bound to the context');
shouldCompileTo(
'{{awesome}}',
{
awesome: function() {
return 'Awesome';
}
},
'Awesome',
'functions are called and render their output'
);
shouldCompileTo(
'{{awesome}}',
{
awesome: function() {
return this.more;
},
more: 'More awesome'
},
'More awesome',
'functions are bound to the context'
);
});
it('functions with context argument', function() {
shouldCompileTo('{{awesome frank}}',
{awesome: function(context) { return context; },
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
shouldCompileTo(
'{{awesome frank}}',
{
awesome: function(context) {
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
});
it('pathed functions with context argument', function() {
shouldCompileTo('{{bar.awesome frank}}',
{bar: {awesome: function(context) { return context; }},
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
shouldCompileTo(
'{{bar.awesome frank}}',
{
bar: {
awesome: function(context) {
return context;
}
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
});
it('depthed functions with context argument', function() {
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
{awesome: function(context) { return context; },
frank: 'Frank'},
'Frank', 'functions are called with context arguments');
shouldCompileTo(
'{{#with frank}}{{../awesome .}}{{/with}}',
{
awesome: function(context) {
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
});
it('block functions with context argument', function() {
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
{awesome: function(context, options) { return options.fn(context); }},
'inner 1', 'block functions are called with context and options');
shouldCompileTo(
'{{#awesome 1}}inner {{.}}{{/awesome}}',
{
awesome: function(context, options) {
return options.fn(context);
}
},
'inner 1',
'block functions are called with context and options'
);
});
it('depthed block functions with context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
{value: true, awesome: function(context, options) { return options.fn(context); }},
'inner 1', 'block functions are called with context and options');
shouldCompileTo(
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
{
value: true,
awesome: function(context, options) {
return options.fn(context);
}
},
'inner 1',
'block functions are called with context and options'
);
});
it('block functions without context argument', function() {
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
{awesome: function(options) { return options.fn(this); }},
'inner', 'block functions are called with options');
shouldCompileTo(
'{{#awesome}}inner{{/awesome}}',
{
awesome: function(options) {
return options.fn(this);
}
},
'inner',
'block functions are called with options'
);
});
it('pathed block functions without context argument', function() {
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
{foo: {awesome: function() { return this; }}},
'inner', 'block functions are called with options');
shouldCompileTo(
'{{#foo.awesome}}inner{{/foo.awesome}}',
{
foo: {
awesome: function() {
return this;
}
}
},
'inner',
'block functions are called with options'
);
});
it('depthed block functions without context argument', function() {
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
{value: true, awesome: function() { return this; }},
'inner', 'block functions are called with options');
shouldCompileTo(
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
{
value: true,
awesome: function() {
return this;
}
},
'inner',
'block functions are called with options'
);
});
it('paths with hyphens', function() {
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
shouldCompileTo(
'{{foo-bar}}',
{ 'foo-bar': 'baz' },
'baz',
'Paths can contain hyphens (-)'
);
shouldCompileTo(
'{{foo.foo-bar}}',
{ foo: { 'foo-bar': 'baz' } },
'baz',
'Paths can contain hyphens (-)'
);
shouldCompileTo(
'{{foo/foo-bar}}',
{ foo: { 'foo-bar': 'baz' } },
'baz',
'Paths can contain hyphens (-)'
);
});
it('nested paths', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Nested paths access nested objects');
shouldCompileTo(
'Goodbye {{alan/expression}} world!',
{ alan: { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Nested paths access nested objects'
);
});
it('nested paths with empty string value', function() {
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
'Goodbye world!', 'Nested paths access nested objects with empty string');
shouldCompileTo(
'Goodbye {{alan/expression}} world!',
{ alan: { expression: '' } },
'Goodbye world!',
'Nested paths access nested objects with empty string'
);
});
it('literal paths', function() {
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Literal paths can be used');
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Literal paths can be used');
shouldCompileTo(
'Goodbye {{[@alan]/expression}} world!',
{ '@alan': { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Literal paths can be used'
);
shouldCompileTo(
'Goodbye {{[foo bar]/expression}} world!',
{ 'foo bar': { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Literal paths can be used'
);
});
it('literal references', function() {
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo('Goodbye {{"foo\'bar"}} world!', {"foo'bar": 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo(
'Goodbye {{[foo bar]}} world!',
{ 'foo bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
'Goodbye {{"foo bar"}} world!',
{ 'foo bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
"Goodbye {{'foo bar'}} world!",
{ 'foo bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
'Goodbye {{"foo[bar"}} world!',
{ 'foo[bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
'Goodbye {{"foo\'bar"}} world!',
{ "foo'bar": 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
"Goodbye {{'foo\"bar'}} world!",
{ 'foo"bar': 'beautiful' },
'Goodbye beautiful world!'
);
});
it("that current context path ({{.}}) doesn't hit helpers", function() {
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: ');
});
it('complex but empty paths', function() {
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
shouldCompileTo('{{person/name}}', {person: {}}, '');
shouldCompileTo('{{person/name}}', { person: { name: null } }, '');
shouldCompileTo('{{person/name}}', { person: {} }, '');
});
it('this keyword in paths', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
'This keyword in paths evaluates to current context');
var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
shouldCompileTo(
string,
hash,
'goodbyeGoodbyeGOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
hash = {
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
};
shouldCompileTo(
string,
hash,
'helloHelloHELLO',
'This keyword evaluates in more complex paths'
);
});
it('this keyword nested inside path', function() {
shouldThrow(function() {
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
}, Error, 'Invalid path: text/this - 1:13');
shouldThrow(
function() {
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
},
Error,
'Invalid path: text/this - 1:13'
);
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar');
shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar');
});
it('this keyword in helpers', function() {
var helpers = {foo: function(value) {
var helpers = {
foo: function(value) {
return 'bar ' + value;
}};
}
};
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
'This keyword in paths evaluates to current context');
var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
shouldCompileTo(
string,
[hash, helpers],
'bar goodbyebar Goodbyebar GOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
hash = {
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
};
shouldCompileTo(
string,
[hash, helpers],
'bar hellobar Hellobar HELLO',
'This keyword evaluates in more complex paths'
);
});
it('this keyword nested inside helpers param', function() {
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, 'Invalid path: text/this - 1:17');
shouldThrow(
function() {
CompilerContext.compile(string);
},
Error,
'Invalid path: text/this - 1:17'
);
shouldCompileTo(
'{{foo [this]}}',
{foo: function(value) { return value; }, 'this': 'bar'},
'bar');
'{{foo [this]}}',
{
foo: function(value) {
return value;
},
this: 'bar'
},
'bar'
);
shouldCompileTo(
'{{foo text/[this]}}',
{foo: function(value) { return value; }, text: {'this': 'bar'}},
'bar');
'{{foo text/[this]}}',
{
foo: function(value) {
return value;
},
text: { this: 'bar' }
},
'bar'
);
});
it('pass string literals', function() {
shouldCompileTo('{{"foo"}}', {}, '');
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
shouldCompileTo(
'{{#"foo"}}{{.}}{{/"foo"}}',
{ foo: ['bar', 'baz'] },
'barbaz'
);
});
it('pass number literals', function() {
@@ -285,13 +569,21 @@ describe('basic context', function() {
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
shouldCompileTo('{{12.34}}', {}, '');
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
shouldCompileTo(
'{{12.34 1}}',
{
'12.34': function(arg) {
return 'bar' + arg;
}
},
'bar1'
);
});
it('pass boolean literals', function() {
shouldCompileTo('{{true}}', {}, '');
shouldCompileTo('{{true}}', { '': 'foo' }, '');
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
shouldCompileTo('{{false}}', { false: 'foo' }, 'foo');
});
it('should handle literals in subexpression', function() {
@@ -300,6 +592,17 @@ describe('basic context', function() {
return arg;
}
};
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
shouldCompileTo(
'{{foo (false)}}',
[
{
false: function() {
return 'bar';
}
},
helpers
],
'bar'
);
});
});
+255 -98
View File
@@ -1,51 +1,99 @@
describe('blocks', function() {
it('array', function() {
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'Arrays iterate over the contents when not empty');
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'Arrays iterate over the contents when not empty'
);
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'Arrays ignore the contents when empty');
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
});
it('array without data', function() {
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
var string =
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , false],
'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
);
});
it('array with @index', function() {
var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
it('empty block', function() {
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, hash, 'cruel world!',
'Arrays iterate over the contents when not empty');
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
shouldCompileTo(
string,
hash,
'cruel world!',
'Arrays iterate over the contents when not empty'
);
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'Arrays ignore the contents when empty');
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
});
it('block with complex lookup', function() {
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
var hash = {
name: 'Alan',
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
};
shouldCompileTo(string, hash, 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
'Templates can access variables in contexts up the stack with relative path syntax');
shouldCompileTo(
string,
hash,
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
'Templates can access variables in contexts up the stack with relative path syntax'
);
});
it('multiple blocks with complex lookup', function() {
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
var hash = {
name: 'Alan',
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
};
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
});
@@ -59,111 +107,201 @@ describe('blocks', function() {
});
it('block with deep nested complex lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] };
var string =
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
var hash = {
omg: 'OMG!',
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
};
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
});
it('works with cached blocks', function() {
var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
var template = CompilerContext.compile(
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
{ data: false }
);
var result = template({person: [{first: 'Alan', last: 'Johnson'}, {first: 'Alan', last: 'Johnson'}]});
var result = template({
person: [
{ first: 'Alan', last: 'Johnson' },
{ first: 'Alan', last: 'Johnson' }
]
});
equals(result, 'Alan JohnsonAlan Johnson');
});
describe('inverted sections', function() {
it('inverted sections with unset value', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var string =
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {};
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
shouldCompileTo(
string,
hash,
'Right On!',
"Inverted section rendered when value isn't set."
);
});
it('inverted section with false value', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {goodbyes: false};
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
var string =
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = { goodbyes: false };
shouldCompileTo(
string,
hash,
'Right On!',
'Inverted section rendered when value is false.'
);
});
it('inverted section with empty set', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {goodbyes: []};
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
var string =
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = { goodbyes: [] };
shouldCompileTo(
string,
hash,
'Right On!',
'Inverted section rendered when value is empty set.'
);
});
it('block inverted sections', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people' },
'No people'
);
});
it('chained inverted sections', function() {
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'},
'No people');
shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'},
'No people');
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', {none: 'No people'},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
{ none: 'No people' },
'No people'
);
shouldCompileTo(
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
{ none: 'No people' },
'No people'
);
shouldCompileTo(
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
{ none: 'No people' },
'No people'
);
});
it('chained inverted sections with mismatch', function() {
shouldThrow(function() {
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
{ none: 'No people' },
'No people'
);
}, Error);
});
it('block inverted sections with empty arrays', function() {
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
'No people');
shouldCompileTo(
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
{ none: 'No people', people: [] },
'No people'
);
});
});
describe('standalone sections', function() {
it('block standalone else sections', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
shouldCompileTo(
'{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
{ none: 'No people' },
'No people\n'
);
shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
});
it('block standalone else sections can be disabled', function() {
shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
'\nNo people\n\n');
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
'\nNo people\n\n'
);
shouldCompileTo(
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
'\nNo people\n\n');
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
'\nNo people\n\n'
);
});
it('block standalone chained else sections', function() {
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
'No people\n');
shouldCompileTo(
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
shouldCompileTo(
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
});
it('should handle nesting', function() {
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
shouldCompileTo(
'{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
{ data: [1, 3, 5] },
'1\n3\n5\nOK.'
);
});
});
describe('compat mode', function() {
it('block with deep recursive lookup lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
var string =
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'Goodbye cruel OMG!'
);
});
it('block with deep recursive pathed lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
var string =
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
var hash = {
omg: { yes: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
};
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'Goodbye cruel OMG!'
);
});
it('block with missed recursive lookup', function() {
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
var string =
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
var hash = {
omg: { no: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
};
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
shouldCompileTo(
string,
[hash, undefined, undefined, true],
'Goodbye cruel '
);
});
});
@@ -181,9 +319,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply allow undefined return', function() {
var helpers = {
@@ -197,9 +336,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator}}suc{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator}}suc{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply block decorators', function() {
@@ -215,9 +355,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should support nested decorators', function() {
var helpers = {
@@ -235,9 +376,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should apply multiple decorators', function() {
@@ -253,9 +395,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{ hash: {}, helpers: helpers, decorators: decorators },
'success'
);
});
it('should access parent variables', function() {
@@ -271,9 +414,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{#helper}}{{*decorator foo}}{{/helper}}',
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
'success');
'{{#helper}}{{*decorator foo}}{{/helper}}',
{ hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
'success'
);
});
it('should work with root program', function() {
var run;
@@ -285,9 +429,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{*decorator "success"}}',
{hash: {'foo': 'success'}, decorators: decorators},
'');
'{{*decorator "success"}}',
{ hash: { foo: 'success' }, decorators: decorators },
''
);
equals(run, true);
});
it('should fail when accessing variables from root', function() {
@@ -300,9 +445,10 @@ describe('blocks', function() {
}
};
shouldCompileTo(
'{{*decorator foo}}',
{hash: {'foo': 'fail'}, decorators: decorators},
'');
'{{*decorator foo}}',
{ hash: { foo: 'fail' }, decorators: decorators },
''
);
equals(run, true);
});
@@ -335,12 +481,23 @@ describe('blocks', function() {
equals(handlebarsEnv.decorators.bar, undefined);
});
it('fails with multiple and args', function() {
shouldThrow(function() {
handlebarsEnv.registerDecorator({
world: function() { return 'world!'; },
testHelper: function() { return 'found it!'; }
}, {});
}, Error, 'Arg not supported with multiple decorators');
shouldThrow(
function() {
handlebarsEnv.registerDecorator(
{
world: function() {
return 'world!';
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
});
});
});
+372 -109
View File
@@ -2,64 +2,156 @@ describe('builtin helpers', function() {
describe('#if', function() {
it('if', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
'if with boolean argument shows the contents when true');
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
'if with string argument shows the contents');
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
'if with boolean argument does not show the contents when false');
shouldCompileTo(string, {world: 'world'}, 'cruel world!',
'if with undefined does not show the contents');
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
'if with non-empty array shows the contents');
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
'if with empty array does not show the contents');
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
'if with zero does not show the contents');
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
'if with zero does not show the contents');
shouldCompileTo(
string,
{ goodbye: true, world: 'world' },
'GOODBYE cruel world!',
'if with boolean argument shows the contents when true'
);
shouldCompileTo(
string,
{ goodbye: 'dummy', world: 'world' },
'GOODBYE cruel world!',
'if with string argument shows the contents'
);
shouldCompileTo(
string,
{ goodbye: false, world: 'world' },
'cruel world!',
'if with boolean argument does not show the contents when false'
);
shouldCompileTo(
string,
{ world: 'world' },
'cruel world!',
'if with undefined does not show the contents'
);
shouldCompileTo(
string,
{ goodbye: ['foo'], world: 'world' },
'GOODBYE cruel world!',
'if with non-empty array shows the contents'
);
shouldCompileTo(
string,
{ goodbye: [], world: 'world' },
'cruel world!',
'if with empty array does not show the contents'
);
shouldCompileTo(
string,
{ goodbye: 0, world: 'world' },
'cruel world!',
'if with zero does not show the contents'
);
shouldCompileTo(
'{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
{ goodbye: 0, world: 'world' },
'GOODBYE cruel world!',
'if with zero does not show the contents'
);
});
it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns true');
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
'if with function shows the contents when function returns string');
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns false');
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
'if with function does not show the contents when returns undefined');
shouldCompileTo(
string,
{
goodbye: function() {
return true;
},
world: 'world'
},
'GOODBYE cruel world!',
'if with function shows the contents when function returns true'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.world;
},
world: 'world'
},
'GOODBYE cruel world!',
'if with function shows the contents when function returns string'
);
shouldCompileTo(
string,
{
goodbye: function() {
return false;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns false'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.foo;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns undefined'
);
});
it('should not change the depth list', function() {
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
var string =
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
shouldCompileTo(
string,
{ foo: { goodbye: true }, world: 'world' },
'GOODBYE cruel world!'
);
});
});
describe('#with', function() {
it('with', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
shouldCompileTo(
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
});
it('with with function argument', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
shouldCompileTo(
string,
{
person: function() {
return { first: 'Alan', last: 'Johnson' };
}
},
'Alan Johnson'
);
});
it('with with else', function() {
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
var string =
'{{#with person}}Person is present{{else}}Person is not present{{/with}}';
shouldCompileTo(string, {}, 'Person is not present');
});
it('with provides block parameter', function() {
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
shouldCompileTo(
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
});
it('works when data is disabled', function() {
var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
var template = CompilerContext.compile(
'{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}',
{ data: false }
);
var result = template({person: {first: 'Alan', last: 'Johnson'}});
var result = template({ person: { first: 'Alan', last: 'Johnson' } });
equals(result, 'Alan Johnson');
});
});
@@ -73,92 +165,179 @@ describe('builtin helpers', function() {
it('each', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'each with array argument ignores the contents when empty');
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty'
);
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'each with array argument ignores the contents when empty'
);
});
it('each without data', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , , false],
'goodbye! Goodbye! GOODBYE! cruel world!'
);
hash = {goodbyes: 'cruel', world: 'world'};
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
hash = { goodbyes: 'cruel', world: 'world' };
shouldCompileTo(
'{{#each .}}{{.}}{{/each}}',
[hash, , , , false],
'cruelworld'
);
});
it('each without context', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
shouldCompileTo(string, [,,,, ], 'cruel !');
shouldCompileTo(string, [, , , ,], 'cruel !');
});
it('each with an object and @key', function() {
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
var string =
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
function Clazz() {
this['<b>#1</b>'] = {text: 'goodbye'};
this[2] = {text: 'GOODBYE'};
this['<b>#1</b>'] = { text: 'goodbye' };
this[2] = { text: 'GOODBYE' };
}
Clazz.prototype.foo = 'fail';
var hash = {goodbyes: new Clazz(), world: 'world'};
var hash = { goodbyes: new Clazz(), world: 'world' };
// Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash);
var expected1 = '&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 = '2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
var expected1 =
'&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 =
'2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
equals(
actual === expected1 || actual === expected2,
true,
'each with object argument iterates over the contents when not empty'
);
shouldCompileTo(string, { goodbyes: {}, world: 'world' }, 'cruel world!');
});
it('each with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
it('each with nested @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!', 'The @index variable is used');
equal(
result,
'0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!',
'The @index variable is used'
);
});
it('each with block params', function() {
var string = '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'};
var string =
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
equal(
result,
'0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'
);
});
it('each object with @index', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
var string =
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
c: { text: 'GOODBYE' }
},
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
});
it('each with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -167,18 +346,34 @@ describe('builtin helpers', function() {
});
it('each with nested @first', function() {
var string = '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
equal(
result,
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!',
'The @first variable is used'
);
});
it('each object with @first', function() {
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var string =
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -187,8 +382,16 @@ describe('builtin helpers', function() {
});
it('each with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -197,8 +400,12 @@ describe('builtin helpers', function() {
});
it('each object with @last', function() {
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var string =
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
var hash = {
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -207,37 +414,78 @@ describe('builtin helpers', function() {
});
it('each with nested @last', function() {
var string = '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var string =
'{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
equal(
result,
'(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!',
'The @last variable is used'
);
});
it('each with function argument', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array function argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
'each with array function argument ignores the contents when empty');
var hash = {
goodbyes: function() {
return [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
];
},
world: 'world'
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array function argument iterates over the contents when not empty'
);
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'each with array function argument ignores the contents when empty'
);
});
it('each object when last key is an empty string', function() {
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'};
var string =
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
var hash = {
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
'': { text: 'GOODBYE' }
},
world: 'world'
};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'Empty string key is not skipped');
equal(
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'Empty string key is not skipped'
);
});
it('data passed to helpers', function() {
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
var hash = {letters: ['a', 'b', 'c']};
var hash = { letters: ['a', 'b', 'c'] };
var template = CompilerContext.compile(string);
var result = template(hash, {
@@ -249,10 +497,16 @@ describe('builtin helpers', function() {
});
it('each on implicit context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
template({});
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
shouldThrow(
function() {
var template = CompilerContext.compile(
'{{#each}}{{text}}! {{/each}}cruel world!'
);
template({});
},
handlebarsEnv.Exception,
'Must pass iterator to #each'
);
});
if (global.Symbol && global.Symbol.iterator) {
@@ -276,13 +530,25 @@ describe('builtin helpers', function() {
return new Iterator(this.arr);
};
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var goodbyes = new Iterable([{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]);
var goodbyes = new Iterable([
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
]);
var goodbyesEmpty = new Iterable([]);
var hash = {goodbyes: goodbyes, world: 'world'};
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty');
shouldCompileTo(string, {goodbyes: goodbyesEmpty, world: 'world'}, 'cruel world!',
'each with array argument ignores the contents when empty');
var hash = { goodbyes: goodbyes, world: 'world' };
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty'
);
shouldCompileTo(
string,
{ goodbyes: goodbyesEmpty, world: 'world' },
'cruel world!',
'each with array argument ignores the contents when empty'
);
});
}
});
@@ -293,9 +559,7 @@ describe('builtin helpers', function() {
return;
}
var $log,
$info,
$error;
var $log, $info, $error;
beforeEach(function() {
$log = console.log;
$info = console.info;
@@ -331,7 +595,7 @@ describe('builtin helpers', function() {
logArg = arg;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals('03', levelArg);
equals('whee', logArg);
});
@@ -367,13 +631,13 @@ describe('builtin helpers', function() {
console.error = $error;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called);
});
it('should handle missing logger', function() {
var string = '{{log blah}}';
var hash = { blah: 'whee' },
called = false;
called = false;
console.error = undefined;
console.log = function(log) {
@@ -382,7 +646,7 @@ describe('builtin helpers', function() {
console.log = $log;
};
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
shouldCompileTo(string, [hash, , , , { level: '03' }], '');
equals(true, called);
});
@@ -396,12 +660,12 @@ describe('builtin helpers', function() {
called = true;
};
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
shouldCompileTo(string, [hash, , , , { level: 'error' }], '');
equals(true, called);
called = false;
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], '');
equals(true, called);
});
it('should handle hash log levels', function() {
@@ -449,11 +713,10 @@ describe('builtin helpers', function() {
/* eslint-enable no-console */
});
describe('#lookup', function() {
it('should lookup arbitrary content', function() {
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
var template = CompilerContext.compile(string);
var result = template(hash);
@@ -462,7 +725,7 @@ describe('builtin helpers', function() {
});
it('should not fail on undefined value', function() {
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
hash = { goodbyes: [0, 1], data: ['foo', 'bar'] };
var template = CompilerContext.compile(string);
var result = template(hash);
+112 -35
View File
@@ -13,37 +13,84 @@ describe('compiler', function() {
equal(compile('foo').equals(compile('foo')), true);
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
equal(
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
compile('{{foo.bar baz "foo" true false bat=1}}')
),
true
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (baz bat=1)}}')
),
true
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
true
);
});
it('should treat as not equal', function() {
equal(compile('foo').equals(compile('bar')), false);
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
equal(
compile('{{foo.bar baz bat=1}}').equals(
compile('{{foo.bar bar bat=1}}')
),
false
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (bar bat=1)}}')
),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(
compile('{{#foo}} {{foo}}{{/foo}}')
),
false
);
});
});
describe('#compile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.compile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
shouldThrow(function() {
Handlebars.compile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
shouldThrow(
function() {
Handlebars.compile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function() {
Handlebars.compile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
});
it('should include the location in the error (row and column)', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
equal(
err.message,
"if doesn't match def - 2:5",
'Checking error message'
);
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
@@ -57,17 +104,28 @@ describe('compiler', function() {
it('should include the location as enumerable property', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(true, false, 'Statement must throw exception. This line should not be executed.');
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(Object.prototype.propertyIsEnumerable.call(err, 'column'), true, 'Checking error column');
equal(
Object.prototype.propertyIsEnumerable.call(err, 'column'),
true,
'Checking error column'
);
}
});
it('can utilize AST instance', function() {
equal(Handlebars.compile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})(), 'Hello');
equal(
Handlebars.compile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})(),
'Hello'
);
});
it('can pass through an empty string', function() {
@@ -75,33 +133,52 @@ describe('compiler', function() {
});
it('should not modify the options.data property(GH-1327)', function() {
var options = {data: [{a: 'foo'}, {a: 'bar'}]};
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
);
});
it('should not modify the options.knownHelpers property(GH-1327)', function() {
var options = {knownHelpers: {}};
var options = { knownHelpers: {} };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ knownHelpers: {} }, 0, 2)
);
});
});
describe('#precompile', function() {
it('should fail with invalid input', function() {
shouldThrow(function() {
Handlebars.precompile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
shouldThrow(function() {
Handlebars.precompile({});
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
shouldThrow(
function() {
Handlebars.precompile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
);
shouldThrow(
function() {
Handlebars.precompile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
);
});
it('can utilize AST instance', function() {
equal(/return "Hello"/.test(Handlebars.precompile({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
})), true);
equal(
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})
),
true
);
});
it('can pass through an empty string', function() {
+141 -48
View File
@@ -1,6 +1,6 @@
describe('data', function() {
it('passing in data to a compiled function that expects data - works with helpers', function() {
var template = CompilerContext.compile('{{hello}}', {data: true});
var template = CompilerContext.compile('{{hello}}', { data: true });
var helpers = {
hello: function(options) {
@@ -8,7 +8,10 @@ describe('data', function() {
}
};
var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ noun: 'cat' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper');
});
@@ -19,7 +22,9 @@ describe('data', function() {
});
it('deep @foo triggers automatic top-level data', function() {
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
var template = CompilerContext.compile(
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
);
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
@@ -47,7 +52,11 @@ describe('data', function() {
};
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('hash values can be looked up via @foo', function() {
@@ -59,7 +68,11 @@ describe('data', function() {
};
var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('nested parameter data can be looked up via @foo.bar', function() {
@@ -70,8 +83,15 @@ describe('data', function() {
}
};
var result = template({}, { helpers: helpers, data: { world: {bar: 'world' } } });
equals('Hello world', result, '@foo as a parameter retrieves template data');
var result = template(
{},
{ helpers: helpers, data: { world: { bar: 'world' } } }
);
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
});
it('nested parameter data does not fail with @world.bar', function() {
@@ -82,8 +102,15 @@ describe('data', function() {
}
};
var result = template({}, { helpers: helpers, data: { foo: {bar: 'world' } } });
equals('Hello undefined', result, '@foo as a parameter retrieves template data');
var result = template(
{},
{ helpers: helpers, data: { foo: { bar: 'world' } } }
);
equals(
'Hello undefined',
result,
'@foo as a parameter retrieves template data'
);
});
it('parameter data throws when using complex scope references', function() {
@@ -96,17 +123,38 @@ describe('data', function() {
it('data can be functions', function() {
var template = CompilerContext.compile('{{@hello}}');
var result = template({}, { data: { hello: function() { return 'hello'; } } });
var result = template(
{},
{
data: {
hello: function() {
return 'hello';
}
}
}
);
equals('hello', result);
});
it('data can be functions with params', function() {
var template = CompilerContext.compile('{{@hello "hello"}}');
var result = template({}, { data: { hello: function(arg) { return arg; } } });
var result = template(
{},
{
data: {
hello: function(arg) {
return arg;
}
}
}
);
equals('hello', result);
});
it('data is inherited downstream', function() {
var template = CompilerContext.compile('{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}', { data: true });
var template = CompilerContext.compile(
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
{ data: true }
);
var helpers = {
let: function(options) {
var frame = Handlebars.createFrame(options.data);
@@ -115,19 +163,22 @@ describe('data', function() {
frame[prop] = options.hash[prop];
}
}
return options.fn(this, {data: frame});
return options.fn(this, { data: frame });
}
};
var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
var result = template(
{ bar: { baz: 'hello world' } },
{ helpers: helpers, data: {} }
);
equals('2hello world1', result, 'data variables are inherited downstream');
});
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
var template = CompilerContext.compile('{{>myPartial}}', {data: true});
var template = CompilerContext.compile('{{>myPartial}}', { data: true });
var partials = {
myPartial: CompilerContext.compile('{{hello}}', {data: true})
myPartial: CompilerContext.compile('{{hello}}', { data: true })
};
var helpers = {
@@ -136,12 +187,15 @@ describe('data', function() {
}
};
var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
var result = template(
{ noun: 'cat' },
{ helpers: helpers, partials: partials, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper inside partial');
});
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
var template = CompilerContext.compile('{{hello world}}', {data: true});
var template = CompilerContext.compile('{{hello world}}', { data: true });
var helpers = {
hello: function(noun, options) {
@@ -149,12 +203,17 @@ describe('data', function() {
}
};
var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, world: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper');
});
it('passing in data to a compiled function that expects data - works with block helpers', function() {
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {data: true});
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {
data: true
});
var helpers = {
hello: function(options) {
@@ -165,106 +224,140 @@ describe('data', function() {
}
};
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper');
});
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?'});
return options.fn({ exclaim: '?' });
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world?', result, 'Data output by helper');
});
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy', accessData: '#win'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
);
equals('#win happy world?', result, 'Data output by helper');
});
it('you can override inherited data when invoking a helper', function() {
var template = CompilerContext.compile('{{#hello}}{{world zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
return options.fn(
{ exclaim: '?', zomg: 'world' },
{ data: { adjective: 'sad' } }
);
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'planet' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper');
});
it('you can override inherited data when invoking a helper with depth', function() {
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
var template = CompilerContext.compile(
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = {
hello: function(options) {
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
},
world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
}
};
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
var result = template(
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper');
});
describe('@root', function() {
it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile('{{@root.foo}}');
var result = template({foo: 'hello'}, { data: {} });
var result = template({ foo: 'hello' }, { data: {} });
equals('hello', result);
result = template({foo: 'hello'}, {});
result = template({ foo: 'hello' }, {});
equals('hello', result);
});
it('passed root values take priority', function() {
var template = CompilerContext.compile('{{@root.foo}}');
var result = template({}, { data: {root: {foo: 'hello'} } });
var result = template({}, { data: { root: { foo: 'hello' } } });
equals('hello', result);
});
});
describe('nesting', function() {
it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
var result = template({foo: 'hello'}, {
helpers: {
helper: function(options) {
var frame = Handlebars.createFrame(options.data);
frame.depth = options.data.depth + 1;
return options.fn(this, {data: frame});
var template = CompilerContext.compile(
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
);
var result = template(
{ foo: 'hello' },
{
helpers: {
helper: function(options) {
var frame = Handlebars.createFrame(options.data);
frame.depth = options.data.depth + 1;
return options.fn(this, { data: frame });
}
},
data: {
depth: 0
}
},
data: {
depth: 0
}
});
);
equals('2 1 0', result);
});
});
+5 -3
View File
@@ -1,12 +1,11 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
var distHandlebars = fs.readFileSync(
require.resolve('../../' + filename),
'utf-8'
);
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
+48 -18
View File
@@ -1,6 +1,6 @@
var global = (function() {
return this;
}());
})();
var AssertError;
if (Error.captureStackTrace) {
@@ -28,10 +28,19 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
/**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
string,
hashOrArray,
partials,
expected,
message
) {
var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) {
throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
throw new AssertError(
"'" + result + "' should === '" + expected + "': " + message,
shouldCompileToWithPartials
);
}
};
@@ -39,17 +48,18 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.compileWithPartials = function(string, hashOrArray, partials) {
var template,
ary,
options;
var template, ary, options;
if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash;
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
ary = [];
ary.push(hashOrArray[0]); // input
ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]});
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options =
typeof hashOrArray[3] === 'object'
? hashOrArray[3]
: { compat: hashOrArray[3] };
if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4];
@@ -58,18 +68,23 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
ary = [hashOrArray];
}
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
string,
options
);
return template.apply(this, ary);
};
/**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/
*/
global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) {
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
throw new AssertError(
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
equals
);
}
};
@@ -86,8 +101,19 @@ global.shouldThrow = function(callback, type, msg) {
if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught);
}
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
if (
msg &&
!(msg.test ? msg.test(caught.message) : msg === caught.message)
) {
throw new AssertError(
'Throw mismatch: Expected ' +
caught.message +
' to match ' +
msg +
'\n\n' +
caught.stack,
shouldThrow
);
}
}
if (failed) {
@@ -95,18 +121,17 @@ global.shouldThrow = function(callback, type, msg) {
}
};
global.expectTemplate = function(templateAsString) {
return new HandlebarsTestBench(templateAsString);
};
function HandlebarsTestBench(templateAsString) {
this.templateAsString = templateAsString;
this.helpers = {};
this.partials = {};
this.input = {};
this.message = 'Template' + templateAsString + ' does not evaluate to expected output';
this.message =
'Template' + templateAsString + ' does not evaluate to expected output';
this.compileOptions = {};
this.runtimeOptions = {};
}
@@ -146,7 +171,11 @@ HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
};
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg) {
HandlebarsTestBench.prototype.toThrow = function(
errorLike,
errMsgMatcher,
msg
) {
var self = this;
expect(function() {
self._compileAndExeute();
@@ -154,7 +183,8 @@ HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg)
};
HandlebarsTestBench.prototype._compileAndExeute = function() {
var compile = Object.keys(this.partials).length > 0
var compile =
Object.keys(this.partials).length > 0
? CompilerContext.compileWithPartial
: CompilerContext.compile;
-1
View File
@@ -3,7 +3,6 @@ require('./common');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
+12 -8
View File
@@ -1,11 +1,11 @@
/* eslint-disable no-console */
var fs = require('fs'),
Mocha = require('mocha'),
path = require('path');
Mocha = require('mocha'),
path = require('path');
var errors = 0,
testDir = path.dirname(__dirname),
grep = process.argv[2];
testDir = path.dirname(__dirname),
grep = process.argv[2];
// Lazy hack, but whatever
if (grep === '--min') {
@@ -13,9 +13,14 @@ if (grep === '--min') {
grep = undefined;
}
var files = fs.readdirSync(testDir)
.filter(function(name) { return (/.*\.js$/).test(name); })
.map(function(name) { return testDir + path.sep + name; });
var files = fs
.readdirSync(testDir)
.filter(function(name) {
return /.*\.js$/.test(name);
})
.map(function(name) {
return testDir + path.sep + name;
});
if (global.minimizedTest) {
run('./runtime', function() {
@@ -37,7 +42,6 @@ if (global.minimizedTest) {
});
}
function run(env, callback) {
var mocha = new Mocha();
mocha.ui('bdd');
+10 -4
View File
@@ -1,12 +1,11 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
@@ -18,7 +17,10 @@ var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js';
}
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
vm.runInThisContext(
fs.readFileSync(__dirname + '/../../' + filename),
filename
);
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
@@ -30,7 +32,11 @@ global.CompilerContext = {
compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
return compiler.precompile(
precompileTemplate,
precompileOptions,
handlebarsEnv
);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
+735 -239
View File
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -13,7 +13,10 @@ describe('javascript-compiler api', function() {
});
it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(
parent,
name
) {
return parent + '.bar_' + name;
};
/* eslint-disable camelcase */
@@ -43,7 +46,7 @@ describe('javascript-compiler api', function() {
};
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') {
throw new Error('It didn\'t work');
throw new Error("It didn't work");
}
};
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
@@ -54,7 +57,8 @@ describe('javascript-compiler api', function() {
beforeEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
$superCreate =
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
@@ -69,7 +73,9 @@ describe('javascript-compiler api', function() {
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
string
) {
return $superAppend.call(this, [string, ' + "_foo"']);
};
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
+257 -95
View File
@@ -68,7 +68,10 @@ describe('parser', function() {
equals(astFor('{{null}}'), '{{ NULL [] }}\n');
});
it('parses mustaches with undefined and null parameters', function() {
equals(astFor('{{foo undefined null}}'), '{{ PATH:foo [UNDEFINED, NULL] }}\n');
equals(
astFor('{{foo undefined null}}'),
'{{ PATH:foo [UNDEFINED, NULL] }}\n'
);
});
it('parses mustaches with DATA parameters', function() {
@@ -78,23 +81,53 @@ describe('parser', function() {
it('parses mustaches with hash arguments', function() {
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
equals(astFor('{{foo bar=true}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n');
equals(astFor('{{foo bar=false}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n');
equals(astFor('{{foo bar=@baz}}'), '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n');
equals(
astFor('{{foo bar=true}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo bar=false}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
);
equals(
astFor('{{foo bar=@baz}}'),
'{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
);
equals(astFor('{{foo bar=baz bat=bam}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n');
equals(astFor('{{foo bar=baz bat="bam"}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n');
equals(
astFor('{{foo bar=baz bat=bam}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
);
equals(
astFor('{{foo bar=baz bat="bam"}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam"}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=1}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=true}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
equals(astFor('{{foo omg bar=baz bat="bam" baz=false}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
equals(
astFor('{{foo omg bar=baz bat="bam"}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
);
});
it('parses contents followed by a mustache', function() {
equals(astFor('foo bar {{baz}}'), 'CONTENT[ \'foo bar \' ]\n{{ PATH:baz [] }}\n');
equals(
astFor('foo bar {{baz}}'),
"CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
);
});
it('parses a partial', function() {
@@ -108,47 +141,81 @@ describe('parser', function() {
});
it('parses a partial with hash', function() {
equals(astFor('{{> foo bar=bat}}'), '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n');
equals(
astFor('{{> foo bar=bat}}'),
'{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
);
});
it('parses a partial with context and hash', function() {
equals(astFor('{{> foo bar bat=baz}}'), '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n');
equals(
astFor('{{> foo bar bat=baz}}'),
'{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
);
});
it('parses a partial with a complex name', function() {
equals(astFor('{{> shared/partial?.bar}}'), '{{> PARTIAL:shared/partial?.bar }}\n');
equals(
astFor('{{> shared/partial?.bar}}'),
'{{> PARTIAL:shared/partial?.bar }}\n'
);
});
it('parsers partial blocks', function() {
equals(astFor('{{#> foo}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
equals(
astFor('{{#> foo}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('should handle parser block mismatch', function() {
shouldThrow(function() {
astFor('{{#> goodbyes}}{{/hellos}}');
}, Error, (/goodbyes doesn't match hellos/));
shouldThrow(
function() {
astFor('{{#> goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('parsers partial blocks with arguments', function() {
equals(astFor('{{#> foo context hash=value}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
equals(
astFor('{{#> foo context hash=value}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('parses a comment', function() {
equals(astFor('{{! this is a comment }}'), "{{! ' this is a comment ' }}\n");
equals(
astFor('{{! this is a comment }}'),
"{{! ' this is a comment ' }}\n"
);
});
it('parses a multi-line comment', function() {
equals(astFor('{{!\nthis is a multi-line comment\n}}'), "{{! '\nthis is a multi-line comment\n' }}\n");
equals(
astFor('{{!\nthis is a multi-line comment\n}}'),
"{{! '\nthis is a multi-line comment\n' }}\n"
);
});
it('parses an inverse section', function() {
equals(astFor('{{#foo}} bar {{^}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses an inverse (else-style) section', function() {
equals(astFor('{{#foo}} bar {{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses multiple inverse sections', function() {
equals(astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n");
equals(
astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses empty blocks', function() {
@@ -156,31 +223,52 @@ describe('parser', function() {
});
it('parses empty blocks with empty inverse section', function() {
equals(astFor('{{#foo}}{{^}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
equals(
astFor('{{#foo}}{{^}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses empty blocks with empty inverse (else-style) section', function() {
equals(astFor('{{#foo}}{{else}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
equals(
astFor('{{#foo}}{{else}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses non-empty blocks with empty inverse section', function() {
equals(astFor('{{#foo}} bar {{^}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
equals(
astFor('{{#foo}} bar {{^}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals(astFor('{{#foo}} bar {{else}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
equals(
astFor('{{#foo}} bar {{else}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses empty blocks with non-empty inverse section', function() {
equals(astFor('{{#foo}}{{^}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
equals(
astFor('{{#foo}}{{^}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals(astFor('{{#foo}}{{else}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
equals(
astFor('{{#foo}}{{else}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses a standalone inverse section', function() {
equals(astFor('{{^foo}}bar{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n");
equals(
astFor('{{^foo}}bar{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
);
});
it('throws on old inverse section', function() {
shouldThrow(function() {
@@ -189,105 +277,179 @@ describe('parser', function() {
});
it('parses block with block params', function() {
equals(astFor('{{#foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{#foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses inverse block with block params', function() {
equals(astFor('{{^foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{^foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses chained inverse block with block params', function() {
equals(astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
equals(
astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('raises if there\'s a Parse error', function() {
shouldThrow(function() {
astFor('foo{{^}}bar');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{foo}');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{foo &}}');
}, Error, /Parse error on line 1/);
shouldThrow(function() {
astFor('{{#goodbyes}}{{/hellos}}');
}, Error, /goodbyes doesn't match hellos/);
it("raises if there's a Parse error", function() {
shouldThrow(
function() {
astFor('foo{{^}}bar');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo &}}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{#goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
shouldThrow(function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
}, Error, /goodbyes doesn't match hellos/);
shouldThrow(
function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('should handle invalid paths', function() {
shouldThrow(function() {
astFor('{{foo/../bar}}');
}, Error, /Invalid path: foo\/\.\. - 1:2/);
shouldThrow(function() {
astFor('{{foo/./bar}}');
}, Error, /Invalid path: foo\/\. - 1:2/);
shouldThrow(function() {
astFor('{{foo/this/bar}}');
}, Error, /Invalid path: foo\/this - 1:2/);
shouldThrow(
function() {
astFor('{{foo/../bar}}');
},
Error,
/Invalid path: foo\/\.\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/./bar}}');
},
Error,
/Invalid path: foo\/\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/this/bar}}');
},
Error,
/Invalid path: foo\/this - 1:2/
);
});
it('knows how to report the correct line number in errors', function() {
shouldThrow(function() {
astFor('hello\nmy\n{{foo}');
}, Error, /Parse error on line 3/);
shouldThrow(function() {
astFor('hello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 5/);
shouldThrow(
function() {
astFor('hello\nmy\n{{foo}');
},
Error,
/Parse error on line 3/
);
shouldThrow(
function() {
astFor('hello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 5/
);
});
it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(function() {
astFor('\n\nhello\n\nmy\n\n{{foo}');
}, Error, /Parse error on line 7/);
shouldThrow(
function() {
astFor('\n\nhello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 7/
);
});
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
equals(astFor({
type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}]
}), 'CONTENT[ \'Hello\' ]\n');
equals(
astFor({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
}),
"CONTENT[ 'Hello' ]\n"
);
});
});
describe('directives', function() {
it('should parse block directives', function() {
equals(astFor('{{#* foo}}{{/foo}}'), 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
equals(
astFor('{{#* foo}}{{/foo}}'),
'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
);
});
it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
});
it('should fail if directives have inverse', function() {
shouldThrow(function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
}, Error, /Unexpected inverse/);
shouldThrow(
function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
},
Error,
/Unexpected inverse/
);
});
});
it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse('\n'
+ ' {{#if foo}}\n'
+ ' {{bar}}\n'
+ ' {{else}} {{baz}}\n'
+ '\n'
+ ' {{/if}}\n'
+ ' ');
var p = Handlebars.parse(
'\n' +
' {{#if foo}}\n' +
' {{bar}}\n' +
' {{else}} {{baz}}\n' +
'\n' +
' {{/if}}\n' +
' '
);
// We really need a deep equals but for now this should be stable...
equals(JSON.stringify(p.loc), JSON.stringify({
start: { line: 1, column: 0 },
end: { line: 7, column: 4 }
}));
equals(JSON.stringify(p.body[1].program.loc), JSON.stringify({
start: { line: 2, column: 13 },
end: { line: 4, column: 7 }
}));
equals(JSON.stringify(p.body[1].inverse.loc), JSON.stringify({
start: { line: 4, column: 15 },
end: { line: 6, column: 5 }
}));
equals(
JSON.stringify(p.loc),
JSON.stringify({
start: { line: 1, column: 0 },
end: { line: 7, column: 4 }
})
);
equals(
JSON.stringify(p.body[1].program.loc),
JSON.stringify({
start: { line: 2, column: 13 },
end: { line: 4, column: 7 }
})
);
equals(
JSON.stringify(p.body[1].inverse.loc),
JSON.stringify({
start: { line: 4, column: 15 },
end: { line: 6, column: 5 }
})
);
});
});
+504 -162
View File
@@ -2,115 +2,224 @@ describe('partials', function() {
it('basic partials', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
});
it('dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
partial: function() {
return 'dude';
}
};
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
});
it('failing dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
partial: function() {
return 'missing';
}
};
shouldThrow(function() {
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
}, Handlebars.Exception, 'The partial missing could not be found');
shouldThrow(
function() {
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
},
Handlebars.Exception,
'The partial missing could not be found'
);
});
it('partials with context', function() {
var string = 'Dudes: {{>dude dudes}}';
var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Partials can be passed a context');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Partials can be passed a context'
);
});
it('partials with no context', function() {
var partial = '{{name}} ({{url}}) ';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}],
true,
'Dudes: () () ');
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, { dude: partial }, { explicitPartialContext: true }],
true,
'Dudes: () () '
);
shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}],
true,
'Dudes: foo () foo () ');
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, { dude: partial }, { explicitPartialContext: true }],
true,
'Dudes: foo () foo () '
);
});
it('partials with string context', function() {
var string = 'Dudes: {{>dude "dudes"}}';
var partial = '{{.}}';
var hash = {};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: dudes');
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: dudes'
);
});
it('partials with undefined context', function() {
var string = 'Dudes: {{>dude dudes}}';
var partial = '{{foo}} Empty';
var hash = {};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Empty');
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Empty'
);
});
it('partials with duplicate parameters', function() {
shouldThrow(function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
}, Error, 'Unsupported number of partial arguments: 2 - 1:7');
shouldThrow(
function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
},
Error,
'Unsupported number of partial arguments: 2 - 1:7'
);
});
it('partials with parameters', function() {
var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
var partial = '{{others.foo}}{{name}} ({{url}}) ';
var hash = {foo: 'bar', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
'Basic partials output based on current context.');
var hash = {
foo: 'bar',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
'Basic partials output based on current context.'
);
});
it('partial in a partial', function() {
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
var dude = '{{name}} {{> url}} ';
var url = '<a href="{{url}}">{{url}}</a>';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, 'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ', 'Partials are rendered inside of other partials');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ',
'Partials are rendered inside of other partials'
);
});
it('rendering undefined partial throws an exception', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('registering undefined partial throws an exception', function() {
shouldThrow(function() {
var undef;
handlebarsEnv.registerPartial('undefined_test', undef);
}, Handlebars.Exception, 'Attempting to register a partial called "undefined_test" as undefined');
shouldThrow(
function() {
var undef;
handlebarsEnv.registerPartial('undefined_test', undef);
},
Handlebars.Exception,
'Attempting to register a partial called "undefined_test" as undefined'
);
});
it('rendering template partial in vm mode throws an exception', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('rendering function partial in vm mode', function() {
@@ -118,30 +227,57 @@ describe('partials', function() {
function partial(context) {
return context.name + ' (' + context.url + ') ';
}
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileTo(string, [hash, {}, {dude: partial}], 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Function partials output based in VM.');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileTo(
string,
[hash, {}, { dude: partial }],
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Function partials output based in VM.'
);
});
it('GH-14: a partial preceding a selector', function() {
var string = 'Dudes: {{>dude}} {{anotherDude}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, 'Dudes: Jeepers Creepers', 'Regular selectors can follow a partial');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes: Jeepers Creepers',
'Regular selectors can follow a partial'
);
});
it('Partials with slash paths', function() {
var string = 'Dudes: {{> shared/dude}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with slash and point paths', function() {
var string = 'Dudes: {{> shared/dude.thing}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing': dude}], true, 'Dudes: Jeepers', 'Partials can use literal with points in paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude.thing': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal with points in paths'
);
});
it('Global Partials', function() {
@@ -149,8 +285,14 @@ describe('partials', function() {
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
handlebarsEnv.unregisterPartial('globalTest');
equals(handlebarsEnv.partials.globalTest, undefined);
@@ -163,51 +305,95 @@ describe('partials', function() {
});
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
});
it('Partials with integer path', function() {
var string = 'Dudes: {{> 404}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {404: dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 404: dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with complex path', function() {
var string = 'Dudes: {{> 404/asdf?.bar}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with escaped', function() {
var string = 'Dudes: {{> [+404/asdf?.bar]}}';
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with string', function() {
var string = 'Dudes: {{> \'+404/asdf?.bar\'}}';
var string = "Dudes: {{> '+404/asdf?.bar'}}";
var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('should handle empty partial', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: ');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: '
);
});
it('throw on missing partial', function() {
var compile = handlebarsEnv.compile;
handlebarsEnv.compile = undefined;
shouldThrow(function() {
shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], '');
}, Error, /The partial dude could not be compiled/);
shouldThrow(
function() {
shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], '');
},
Error,
/The partial dude could not be compiled/
);
handlebarsEnv.compile = compile;
});
@@ -217,224 +403,309 @@ describe('partials', function() {
'{{#> dude}}success{{/dude}}',
[{}, {}, {}],
true,
'success');
'success'
);
});
it('should execute default block with proper context', function() {
shouldCompileToWithPartials(
'{{#> dude context}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {}],
[{ context: { value: 'success' } }, {}, {}],
true,
'success');
'success'
);
});
it('should propagate block parameters to default block', function() {
shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {}],
[{ context: { value: 'success' } }, {}, {}],
true,
'success');
'success'
);
});
it('should not use partial block if partial exists', function() {
shouldCompileToWithPartials(
'{{#> dude}}fail{{/dude}}',
[{}, {}, {dude: 'success'}],
[{}, {}, { dude: 'success' }],
true,
'success');
'success'
);
});
it('should render block from partial', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }}'}],
[{}, {}, { dude: '{{> @partial-block }}' }],
true,
'success');
'success'
);
});
it('should be able to render the partial-block twice', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}],
true,
'success success');
'{{#> dude}}success{{/dude}}',
[{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }],
true,
'success success'
);
});
it('should render block from partial with context', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success');
'success'
);
});
it('should be able to access the @data frame from a partial-block', function() {
shouldCompileToWithPartials(
'{{#> dude}}in-block: {{@root/value}}{{/dude}}',
[{value: 'success'}, {}, {dude: '<code>before-block: {{@root/value}} {{> @partial-block }}</code>'}],
[
{ value: 'success' },
{},
{
dude:
'<code>before-block: {{@root/value}} {{> @partial-block }}</code>'
}
],
true,
'<code>before-block: success in-block: success</code>');
'<code>before-block: success in-block: success</code>'
);
});
it('should allow the #each-helper to be used along with partial-blocks', function() {
shouldCompileToWithPartials(
'<template>{{#> list value}}value = {{.}}{{/list}}</template>',
[
{value: ['a', 'b', 'c']},
{ value: ['a', 'b', 'c'] },
{},
{
list: '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
list:
'<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
}
],
true,
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>');
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>'
);
});
it('should render block from partial with context (twice)', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}',
[
{context: {value: 'success'}},
{},
{
dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
}
],
true,
'success success');
'{{#> dude}}{{value}}{{/dude}}',
[
{ context: { value: 'success' } },
{},
{
dude:
'{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
}
],
true,
'success success'
);
});
it('should render block from partial with context', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{../context/value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success');
'success'
);
});
it('should render block from partial with block params', function() {
shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {dude: '{{> @partial-block }}'}],
[
{ context: { value: 'success' } },
{},
{ dude: '{{> @partial-block }}' }
],
true,
'success');
'success'
);
});
it('should render nested partial blocks', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>');
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>'
);
});
it('should render nested partial blocks at different nesting levels', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>');
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>'
);
});
it('should render nested partial blocks at different nesting levels (twice)', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>'
}
],
true,
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>');
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>'
);
});
it('should render nested partial blocks (twice at each level)', function() {
shouldCompileToWithPartials(
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
[
{value: 'success'},
{ value: 'success' },
{},
{
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
outer:
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
}
],
true,
'<template><outer>' +
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>');
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>'
);
});
});
describe('inline partials', function() {
it('should define inline partials for template', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
});
it('should overwrite multiple partials in the same template', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
});
it('should define inline partials for block', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
shouldThrow(function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', {}, 'success');
}, Error, /myPartial could not/);
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
shouldThrow(
function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
{},
'success'
);
},
Error,
/myPartial could not/
);
});
it('should override global partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {hash: {}, partials: {myPartial: function() { return 'fail'; }}}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{
hash: {},
partials: {
myPartial: function() {
return 'fail';
}
}
},
'success'
);
});
it('should override template partials', function() {
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
});
it('should override partials down the entire stack', function() {
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', {}, 'success');
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}',
{},
'success'
);
});
it('should define inline partials for partial call', function() {
shouldCompileToWithPartials(
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success');
'success'
);
});
it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, {dude: '{{> myPartial }}'}],
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success');
'success'
);
});
it('should render nested inline partials', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success</outer-block></inner>');
'<inner><outer-block>success</outer-block></inner>'
);
});
it('should render nested inline partials with partial-blocks on different nesting levels', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success</outer-block></inner>success');
'<inner><outer-block>success</outer-block></inner>success'
);
});
it('should render nested inline partials (twice at each level)', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}],
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>');
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>'
);
});
});
@@ -442,8 +713,8 @@ describe('partials', function() {
if (Handlebars.compile) {
var env = Handlebars.create();
env.registerPartial('partial', '{{foo}}');
var template = env.compile('{{foo}} {{> partial}}', {noEscape: true});
equal(template({foo: '<'}), '< <');
var template = env.compile('{{foo}} {{> partial}}', { noEscape: true });
equal(template({ foo: '<' }), '< <');
}
});
@@ -451,25 +722,52 @@ describe('partials', function() {
it('indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true,
'Dudes:\n Yehuda\n Alan\n');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes:\n Yehuda\n Alan\n'
);
});
it('nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'
);
});
it('prevent nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n';
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}, {preventIndent: true}], true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }, { preventIndent: true }],
true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'
);
});
});
@@ -477,26 +775,70 @@ describe('partials', function() {
it('partials can access parents', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials can access parents with custom context', function() {
var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials can access parents without data', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true, false], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true, false],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials inherit compat', function() {
var string = 'Dudes: {{> dude}}';
var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
});
});
+185 -92
View File
@@ -6,27 +6,24 @@ describe('precompiler', function() {
}
var Handlebars = require('../lib'),
Precompiler = require('../dist/cjs/precompiler'),
fs = require('fs'),
uglify = require('uglify-js');
Precompiler = require('../dist/cjs/precompiler'),
fs = require('fs'),
uglify = require('uglify-js');
var log,
logFunction,
errorLog,
errorLogFunction,
precompile,
minify,
emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars',
name: 'empty',
source: ''
},
file,
content,
writeFileSync;
logFunction,
errorLog,
errorLogFunction,
precompile,
minify,
emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars',
name: 'empty',
source: ''
},
file,
content,
writeFileSync;
/**
* Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded.
@@ -87,102 +84,166 @@ describe('precompiler', function() {
});
it('should output version', function() {
Precompiler.cli({templates: [], version: true});
Precompiler.cli({ templates: [], version: true });
equals(log, Handlebars.VERSION);
});
it('should throw if lacking templates', function() {
shouldThrow(function() {
Precompiler.cli({templates: []});
}, Handlebars.Exception, 'Must define at least one template or directory.');
shouldThrow(
function() {
Precompiler.cli({ templates: [] });
},
Handlebars.Exception,
'Must define at least one template or directory.'
);
});
it('should handle empty/filtered directories', function() {
Precompiler.cli({hasDirectory: true, templates: []});
Precompiler.cli({ hasDirectory: true, templates: [] });
// Success is not throwing
});
it('should throw when combining simple and minimized', function() {
shouldThrow(function() {
Precompiler.cli({templates: [__dirname], simple: true, min: true});
}, Handlebars.Exception, 'Unable to minimize simple output');
shouldThrow(
function() {
Precompiler.cli({ templates: [__dirname], simple: true, min: true });
},
Handlebars.Exception,
'Unable to minimize simple output'
);
});
it('should throw when combining simple and multiple templates', function() {
shouldThrow(function() {
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
shouldThrow(
function() {
Precompiler.cli({
templates: [
__dirname + '/artifacts/empty.handlebars',
__dirname + '/artifacts/empty.handlebars'
],
simple: true
});
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should throw when missing name', function() {
shouldThrow(function() {
Precompiler.cli({templates: [{source: ''}], amd: true});
}, Handlebars.Exception, 'Name missing for template');
shouldThrow(
function() {
Precompiler.cli({ templates: [{ source: '' }], amd: true });
},
Handlebars.Exception,
'Name missing for template'
);
});
it('should throw when combining simple and directories', function() {
shouldThrow(function() {
Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
shouldThrow(
function() {
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should output simple templates', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true });
equal(log, 'simple\n');
});
it('should default to simple templates', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [{source: ''}]});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [{ source: '' }] });
equal(log, 'simple\n');
});
it('should output amd templates', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], amd: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true });
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
namespace: 'foo'
});
equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output amd partials', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd partials', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
partial: true
});
equal(/return Handlebars\.partials\[/.test(log), false);
equal(/template\(amd\)/.test(log), true);
});
it('should output commonjs templates', function() {
Handlebars.precompile = function() { return 'commonjs'; };
Precompiler.cli({templates: [emptyTemplate], commonjs: true});
Handlebars.precompile = function() {
return 'commonjs';
};
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
equal(/template\(commonjs\)/.test(log), true);
});
it('should set data flag', function() {
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true});
Handlebars.precompile = function(data, options) {
equal(options.data, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, data: true });
equal(log, 'simple\n');
});
it('should set known helpers', function() {
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'});
Handlebars.precompile = function(data, options) {
equal(options.knownHelpers.foo, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
equal(log, 'simple\n');
});
it('should output to file system', function() {
Handlebars.precompile = function() { return 'simple'; };
Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'});
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({
templates: [emptyTemplate],
simple: true,
output: 'file!'
});
equal(file, 'file!');
equal(content, 'simple\n');
equal(log, '');
});
it('should output minimized templates', function() {
Handlebars.precompile = function() { return 'amd'; };
uglify.minify = function() { return {code: 'min'}; };
Precompiler.cli({templates: [emptyTemplate], min: true});
Handlebars.precompile = function() {
return 'amd';
};
uglify.minify = function() {
return { code: 'min' };
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(log, 'min');
});
@@ -191,8 +252,10 @@ describe('precompiler', function() {
error.code = 'MODULE_NOT_FOUND';
mockRequireUglify(error, function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], min: true});
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(/template\(amd\)/.test(log), true);
equal(/\n/.test(log), true);
equal(/Code minimization is disabled/.test(errorLog), true);
@@ -201,23 +264,33 @@ describe('precompiler', function() {
it('should fail on errors (other than missing module) while loading uglify-js', function() {
mockRequireUglify(new Error('Mock Error'), function() {
shouldThrow(function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [emptyTemplate], min: true});
}, Error, 'Mock Error');
shouldThrow(
function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
},
Error,
'Mock Error'
);
});
});
it('should output map', function() {
Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'});
Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
});
it('should output map', function() {
Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'});
Precompiler.cli({
templates: [emptyTemplate],
min: true,
map: 'foo.js.map'
});
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
@@ -225,35 +298,48 @@ describe('precompiler', function() {
describe('#loadTemplates', function() {
it('should throw on missing template', function(done) {
Precompiler.loadTemplates({files: ['foo']}, function(err) {
Precompiler.loadTemplates({ files: ['foo'] }, function(err) {
equal(err.message, 'Unable to open template file "foo"');
done();
});
});
it('should enumerate directories by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hbs'}, function(err, opts) {
equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2');
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
function(err, opts) {
equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2');
done(err);
}
);
});
it('should enumerate all templates by extension', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'handlebars'}, function(err, opts) {
equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1');
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
function(err, opts) {
equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1');
done(err);
}
);
});
it('should handle regular expression characters in extensions', function(done) {
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hb(s'}, function(err) {
// Success is not throwing
done(err);
});
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
function(err) {
// Success is not throwing
done(err);
}
);
});
it('should handle BOM', function(done) {
var opts = {files: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true};
var opts = {
files: [__dirname + '/artifacts/bom.handlebars'],
extension: 'handlebars',
bom: true
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].source, 'a');
done(err);
@@ -261,7 +347,11 @@ describe('precompiler', function() {
});
it('should handle different root', function(done) {
var opts = {files: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'};
var opts = {
files: [__dirname + '/artifacts/empty.handlebars'],
simple: true,
root: 'foo/'
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
done(err);
@@ -269,7 +359,7 @@ describe('precompiler', function() {
});
it('should accept string inputs', function(done) {
var opts = {string: ''};
var opts = { string: '' };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, undefined);
equal(opts.templates[0].source, '');
@@ -277,7 +367,7 @@ describe('precompiler', function() {
});
});
it('should accept string array inputs', function(done) {
var opts = {string: ['', 'bar'], name: ['beep', 'boop']};
var opts = { string: ['', 'bar'], name: ['beep', 'boop'] };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, 'beep');
equal(opts.templates[0].source, '');
@@ -288,7 +378,7 @@ describe('precompiler', function() {
});
it('should accept stdin input', function(done) {
var stdin = require('mock-stdin').stdin();
Precompiler.loadTemplates({string: '-'}, function(err, opts) {
Precompiler.loadTemplates({ string: '-' }, function(err, opts) {
equal(opts.templates[0].source, 'foo');
done(err);
});
@@ -297,9 +387,12 @@ describe('precompiler', function() {
stdin.end();
});
it('error on name missing', function(done) {
var opts = {string: ['', 'bar']};
var opts = { string: ['', 'bar'] };
Precompiler.loadTemplates(opts, function(err) {
equal(err.message, 'Number of names did not match the number of string inputs');
equal(
err.message,
'Number of names did not match the number of string inputs'
);
done();
});
});
+222 -107
View File
@@ -1,93 +1,134 @@
describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() {
var data = {
'books': [{
'title': 'The origin of species',
'author': {
'name': 'Charles Darwin'
books: [
{
title: 'The origin of species',
author: {
name: 'Charles Darwin'
}
},
{
title: 'Lazarillo de Tormes'
}
}, {
'title': 'Lazarillo de Tormes'
}]
]
};
var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
shouldCompileTo(string, data, 'The origin of speciesCharles DarwinLazarillo de Tormes',
'Renders without an undefined property error');
shouldCompileTo(
string,
data,
'The origin of speciesCharles DarwinLazarillo de Tormes',
'Renders without an undefined property error'
);
});
it("GH-150: Inverted sections print when they shouldn't", function() {
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
shouldCompileTo(string, {}, 'not set :: ', "inverted sections run when property isn't present in context");
shouldCompileTo(string, {set: undefined}, 'not set :: ', 'inverted sections run when property is undefined');
shouldCompileTo(string, {set: false}, 'not set :: ', 'inverted sections run when property is false');
shouldCompileTo(string, {set: true}, ' :: set', "inverted sections don't run when property is true");
shouldCompileTo(
string,
{},
'not set :: ',
"inverted sections run when property isn't present in context"
);
shouldCompileTo(
string,
{ set: undefined },
'not set :: ',
'inverted sections run when property is undefined'
);
shouldCompileTo(
string,
{ set: false },
'not set :: ',
'inverted sections run when property is false'
);
shouldCompileTo(
string,
{ set: true },
' :: set',
"inverted sections don't run when property is true"
);
});
it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}';
var data = { 'arr': [1, 2] };
var data = { arr: [1, 2] };
shouldCompileTo(string, data, '1, 2', 'it works as expected');
});
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
+ '{{! Just ignore this business. }}\n'
+ 'Check this out:\n'
+ '{{#hasThings}}\n'
+ '<ul>\n'
+ '{{#things}}\n'
+ '<li class={{className}}>{{word}}</li>\n'
+ '{{/things}}</ul>.\n'
+ '{{/hasThings}}\n'
+ '{{^hasThings}}\n'
+ '\n'
+ '<small>Nothing to check out...</small>\n'
+ '{{/hasThings}}';
var string =
'<strong>This is a slightly more complicated {{thing}}.</strong>.\n' +
'{{! Just ignore this business. }}\n' +
'Check this out:\n' +
'{{#hasThings}}\n' +
'<ul>\n' +
'{{#things}}\n' +
'<li class={{className}}>{{word}}</li>\n' +
'{{/things}}</ul>.\n' +
'{{/hasThings}}\n' +
'{{^hasThings}}\n' +
'\n' +
'<small>Nothing to check out...</small>\n' +
'{{/hasThings}}';
var data = {
thing: function() {
return 'blah';
},
things: [
{className: 'one', word: '@fat'},
{className: 'two', word: '@dhg'},
{className: 'three', word: '@sayrer'}
{ className: 'one', word: '@fat' },
{ className: 'two', word: '@dhg' },
{ className: 'three', word: '@sayrer' }
],
hasThings: function() {
return true;
}
};
var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
+ 'Check this out:\n'
+ '<ul>\n'
+ '<li class=one>@fat</li>\n'
+ '<li class=two>@dhg</li>\n'
+ '<li class=three>@sayrer</li>\n'
+ '</ul>.\n';
var output =
'<strong>This is a slightly more complicated blah.</strong>.\n' +
'Check this out:\n' +
'<ul>\n' +
'<li class=one>@fat</li>\n' +
'<li class=two>@dhg</li>\n' +
'<li class=three>@sayrer</li>\n' +
'</ul>.\n';
shouldCompileTo(string, data, output);
});
it('GH-408: Multiple loops fail', function() {
var context = [
{ name: 'John Doe', location: { city: 'Chicago' } },
{ name: 'Jane Doe', location: { city: 'New York'} }
{ name: 'Jane Doe', location: { city: 'New York' } }
];
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
var template = CompilerContext.compile(
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
);
var result = template(context);
equals(result, 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', 'It should output multiple times');
equals(
result,
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
'It should output multiple times'
);
});
it('GS-428: Nested if else rendering', function() {
var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var succeedingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = {
blk: function(block) { return block.fn(''); },
inverse: function(block) { return block.inverse(''); }
blk: function(block) {
return block.fn('');
},
inverse: function(block) {
return block.inverse('');
}
};
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
@@ -95,7 +136,7 @@ describe('Regressions', function() {
});
it('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
});
it('GH-375: Unicode line terminators', function() {
@@ -104,11 +145,11 @@ describe('Regressions', function() {
it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */
Object.prototype[0xD834] = true;
Object.prototype[0xd834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xD834];
delete Object.prototype[0xd834];
/* eslint-enable no-extend-native */
});
@@ -123,34 +164,46 @@ describe('Regressions', function() {
it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}';
var data = { 'arr': [1, 2] };
var data = { arr: [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
});
it('Mustache man page', function() {
var string = 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
var string =
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
var data = {
'name': 'Chris',
'value': 10000,
'taxed_value': 10000 - (10000 * 0.4),
'in_ca': true
name: 'Chris',
value: 10000,
taxed_value: 10000 - 10000 * 0.4,
in_ca: true
};
shouldCompileTo(string, data, 'Hello Chris. You have just won $10000! Well, $6000, after taxes.', 'the hello world mustache example works');
shouldCompileTo(
string,
data,
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
'the hello world mustache example works'
);
});
it('GH-731: zero context rendering', function() {
shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is ~ ');
shouldCompileTo(
'{{#foo}} This is {{bar}} ~ {{/foo}}',
{ foo: 0, bar: 'OK' },
' This is ~ '
);
});
it('GH-820: zero pathed rendering', function() {
shouldCompileTo('{{foo.bar}}', {foo: 0}, '');
shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
});
it('GH-837: undefined values for helpers', function() {
var helpers = {
str: function(value) { return value + ''; }
str: function(value) {
return value + '';
}
};
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
@@ -159,15 +212,13 @@ describe('Regressions', function() {
it('GH-926: Depths and de-dupe', function() {
var context = {
name: 'foo',
data: [
1
],
notData: [
1
]
data: [1],
notData: [1]
};
var template = CompilerContext.compile('{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}');
var template = CompilerContext.compile(
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
);
var result = template(context);
equals(result, 'foo');
@@ -176,11 +227,15 @@ describe('Regressions', function() {
it('GH-1021: Each empty string key', function() {
var data = {
'': 'foo',
'name': 'Chris',
'value': 10000
name: 'Chris',
value: 10000
};
shouldCompileTo('{{#each data}}Key: {{@key}}\n{{/each}}', {data: data}, 'Key: \nKey: name\nKey: value\n');
shouldCompileTo(
'{{#each data}}Key: {{@key}}\n{{/each}}',
{ data: data },
'Key: \nKey: name\nKey: value\n'
);
});
it('GH-1054: Should handle simple safe string responses', function() {
@@ -194,18 +249,27 @@ describe('Regressions', function() {
}
};
shouldCompileToWithPartials(root, [{}, helpers, partials], true, '<partial>');
shouldCompileToWithPartials(
root,
[{}, helpers, partials],
true,
'<partial>'
);
});
it('GH-1065: Sparse arrays', function() {
var array = [];
array[1] = 'foo';
array[3] = 'bar';
shouldCompileTo('{{#each array}}{{@index}}{{.}}{{/each}}', {array: array}, '1foo3bar');
shouldCompileTo(
'{{#each array}}{{@index}}{{.}}{{/each}}',
{ array: array },
'1foo3bar'
);
});
it('GH-1093: Undefined helper context', function() {
var obj = {foo: undefined, bar: 'bat'};
var obj = { foo: undefined, bar: 'bat' };
var helpers = {
helper: function() {
// It's valid to execute a block against an undefined context, but
@@ -216,28 +280,45 @@ describe('Regressions', function() {
}
}
// And to make IE happy, check for the known string as length is not enumerated.
return (this === 'bat' ? 'found' : 'not');
return this === 'bat' ? 'found' : 'not';
}
};
shouldCompileTo('{{#each obj}}{{{helper}}}{{.}}{{/each}}', [{obj: obj}, helpers], 'notfoundbat');
shouldCompileTo(
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
[{ obj: obj }, helpers],
'notfoundbat'
);
});
it('should support multiple levels of inline partials', function() {
var string = '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var string =
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout: '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
var string = '{{#> layout}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout: '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}';
@@ -249,51 +330,63 @@ describe('Regressions', function() {
});
it('GH-1135 : Context handling within each iteration', function() {
var obj = {array: [1], name: 'John'};
var obj = { array: [1], name: 'John' };
var helpers = {
myif: function(conditional, options) {
if (conditional) {
return options.fn(this);
return options.fn(this);
} else {
return options.inverse(this);
return options.inverse(this);
}
}
};
shouldCompileTo(
'{{#each array}}\n'
+ ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n'
+ ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n'
+ '{{/each}}', [obj, helpers],
' 1. IF: John--\n'
+ ' 2. MYIF: John==\n');
'{{#each array}}\n' +
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
'{{/each}}',
[obj, helpers],
' 1. IF: John--\n' + ' 2. MYIF: John==\n'
);
});
it('GH-1186: Support block params for existing programs', function() {
var string =
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}'
+ '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}'
+ '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', '');
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
});
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', '');
var string =
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
shouldCompileTo(
string,
{ value: 'parent', list: [null, 'a'] },
'parent=parent parent=parent ',
''
);
});
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
var string = 'template {{>partial}} template';
var partials = {
partialWithBlock: '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partialWithBlock:
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'template block partial block template');
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'template block partial block template'
);
});
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
it('should compile and execute templates', function() {
var newHandlebarsInstance = Handlebars.create();
@@ -301,31 +394,53 @@ describe('Regressions', function() {
newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase();
});
var result = newHandlebarsInstance.templates['test.hbs']({name: 'yehuda'});
var result = newHandlebarsInstance.templates['test.hbs']({
name: 'yehuda'
});
equals(result.trim(), 'YEHUDA');
});
it('should call "helperMissing" if a helper is missing', function() {
var newHandlebarsInstance = Handlebars.create();
shouldThrow(function() {
registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.templates['test.hbs']({});
}, Handlebars.Exception, 'Missing helper: "loud"');
shouldThrow(
function() {
registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.templates['test.hbs']({});
},
Handlebars.Exception,
'Missing helper: "loud"'
);
});
// This is a only slightly modified precompiled templated from compiled with 4.2.1
function registerTemplate(Handlebars) {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['test.hbs'] = template({'compiler': [7, '>= 4.0.0'], 'main': function(container, depth0, helpers, partials, data) {
return container.escapeExpression((helpers.loud || (depth0 && depth0.loud) || helpers.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}), (depth0 != null ? depth0.name : depth0), {'name': 'loud', 'hash': {}, 'data': data}))
+ '\n\n';
}, 'useData': true});
var template = Handlebars.template,
templates = (Handlebars.templates = Handlebars.templates || {});
templates['test.hbs'] = template({
compiler: [7, '>= 4.0.0'],
main: function(container, depth0, helpers, partials, data) {
return (
container.escapeExpression(
(
helpers.loud ||
(depth0 && depth0.loud) ||
helpers.helperMissing
).call(
depth0 != null ? depth0 : container.nullContext || {},
depth0 != null ? depth0.name : depth0,
{ name: 'loud', hash: {}, data: data }
)
) + '\n\n'
);
},
useData: true
});
}
});
it('should allow hash with protected array names', function() {
var obj = {array: [1], name: 'John'};
var obj = { array: [1], name: 'John' };
var helpers = {
helpa: function(options) {
return options.hash.length;
@@ -352,7 +467,7 @@ describe('Regressions', function() {
it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({
'dude': 'I am a partial'
dude: 'I am a partial'
});
var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
+2 -2
View File
@@ -5,7 +5,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
equal(template, require('./artifacts/example_1.handlebars'));
var expected = 'foo\n';
var result = template({foo: 'foo'});
var result = template({ foo: 'foo' });
equal(result, expected);
});
@@ -15,7 +15,7 @@ if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
equal(template, require('./artifacts/example_2.hbs'));
var expected = 'Hello, World!\n';
var result = template({name: 'World'});
var result = template({ name: 'World' });
equal(result, expected);
});
+75 -43
View File
@@ -1,34 +1,58 @@
describe('runtime', function() {
describe('#template', function() {
it('should throw on invalid templates', function() {
shouldThrow(function() {
Handlebars.template({});
}, Error, 'Unknown template object: object');
shouldThrow(function() {
Handlebars.template();
}, Error, 'Unknown template object: undefined');
shouldThrow(function() {
Handlebars.template('');
}, Error, 'Unknown template object: string');
shouldThrow(
function() {
Handlebars.template({});
},
Error,
'Unknown template object: object'
);
shouldThrow(
function() {
Handlebars.template();
},
Error,
'Unknown template object: undefined'
);
shouldThrow(
function() {
Handlebars.template('');
},
Error,
'Unknown template object: string'
);
});
it('should throw on version mismatch', function() {
shouldThrow(function() {
Handlebars.template({
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
shouldThrow(function() {
Handlebars.template({
main: {}
});
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
},
Error,
/Template was precompiled with a newer version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {}
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
});
});
@@ -38,34 +62,42 @@ describe('runtime', function() {
}
it('should throw for depthed methods without depths', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
}, Error, 'must pass parent depths');
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass parent depths'
);
});
it('should throw for block param methods without params', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
}, Error, 'must pass block params');
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass block params'
);
});
it('should expose child template', function() {
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
// Calling twice to hit the non-compiled case.
// Calling twice to hit the non-compiled case.
equal(template._child(1)(), 'bar');
equal(template._child(1)(), 'bar');
});
it('should render depthed content', function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
equal(template._child(1, undefined, [], [{bar: 'baz'}])(), 'baz');
// Calling twice to hit the non-compiled case.
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
});
});
+196 -132
View File
@@ -1,144 +1,208 @@
describe('security issues', function() {
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
it('should not allow constructors to be accessed', function() {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
it('should not allow constructors to be accessed', function() {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{constructor.name}}')
.withInput({})
.toCompileTo('');
});
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
.withInput({})
.withHelper('list', function(element) {
return [element];
})
.toCompileTo('');
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo('{{constructor.name}}', {'constructor': {
'name': 'here we go'
}}, 'here we go');
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
'name': 'here we go'
}}, 'here we go');
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
'name': 'here we go'
}}, 'here we go');
});
it('should allow prototype properties that are not constructors', function() {
function TestClass() {
}
Object.defineProperty(TestClass.prototype, 'abc', {
get: function() {
return 'xyz';
}
});
shouldCompileTo('{{#with this as |obj|}}{{obj.abc}}{{/with}}',
new TestClass(), 'xyz');
shouldCompileTo('{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}',
new TestClass(), 'xyz');
});
expectTemplate('{{constructor.name}}')
.withInput({})
.toCompileTo('');
});
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
.withInput({})
.withHelper('list', function(element) {
return [element];
})
.toCompileTo('');
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo(
'{{constructor.name}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
});
it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
});
it('should allow prototype properties that are not constructors', function() {
function TestClass() {}
Object.defineProperty(TestClass.prototype, 'abc', {
get: function() {
return 'xyz';
}
});
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }});
}).to.throw(Error);
expect(functionCalls.length).to.equal(0);
});
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{#blockHelperMissing .}}{{/blockHelperMissing}}');
template({ fn: function() { return 'functionInData';}});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', function() {
var template = Handlebars.compile('{{helperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true});
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}');
template({}, {allowCallsToHelperMissing: true});
});
});
shouldCompileTo(
'{{#with this as |obj|}}{{obj.abc}}{{/with}}',
new TestClass(),
'xyz'
);
shouldCompileTo(
'{{#with this as |obj|}}{{lookup obj "abc"}}{{/with}}',
new TestClass(),
'xyz'
);
});
});
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
}
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({
fn: function() {
functionCalls.push('called');
}
expectTemplate('{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}')
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595', function() {
it('properties, that are required to be enumerable', function() {
expectTemplate('{{constructor}}').withInput({}).toCompileTo('');
expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo('');
expectTemplate('{{__proto__}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo('');
expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo('');
});
}).to.throw(Error);
expect(functionCalls.length).to.equal(0);
});
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() {
var template = Handlebars.compile(
'{{#blockHelperMissing .}}{{/blockHelperMissing}}'
);
template({
fn: function() {
return 'functionInData';
}
});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', function() {
var template = Handlebars.compile('{{helperMissing}}');
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template(
{
fn: function() {
functionCalls.push('called');
}
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile(
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
});
});
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
}
expectTemplate(
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}'
)
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595', function() {
it('properties, that are required to be enumerable', function() {
expectTemplate('{{constructor}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__defineGetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__defineSetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__lookupGetter__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{__proto__}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "constructor"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__defineGetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__defineSetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__lookupGetter__"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{lookup "__proto__"}}')
.withInput({})
.toCompileTo('');
});
});
});
+15 -9
View File
@@ -1,7 +1,7 @@
try {
if (typeof define !== 'function' || !define.amd) {
var SourceMap = require('source-map'),
SourceMapConsumer = SourceMap.SourceMapConsumer;
SourceMapConsumer = SourceMap.SourceMapConsumer;
}
} catch (err) {
/* NOP for in browser */
@@ -13,22 +13,28 @@ describe('source-map', function() {
}
it('should safely include source map info', function() {
var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
var template = Handlebars.precompile('{{hello}}', {
destName: 'dest.js',
srcName: 'src.hbs'
});
equal(!!template.code, true);
equal(!!template.map, !CompilerContext.browser);
});
it('should map source properly', function() {
var templateSource = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {destName: 'dest.js', srcName: 'src.hbs'});
var templateSource =
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {
destName: 'dest.js',
srcName: 'src.hbs'
});
if (template.map) {
var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
var mapped = consumer.originalPositionFor(generated);
equal(mapped.line, source.line);
+26 -14
View File
@@ -5,11 +5,11 @@ describe('spec', function() {
}
var _ = require('underscore'),
fs = require('fs');
fs = require('fs');
var specDir = __dirname + '/mustache/specs/';
var specs = _.filter(fs.readdirSync(specDir), function(name) {
return (/.*\.json$/).test(name);
return /.*\.json$/.test(name);
});
_.each(specs, function(name) {
@@ -17,16 +17,17 @@ describe('spec', function() {
_.each(spec.tests, function(test) {
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec
// We also do not support alternative delimeters
if (name === '~lambdas.json'
// We also choose to throw if paritals are not found
|| (name === 'partials.json' && test.name === 'Failed Lookup')
// We nest the entire response from partials, not just the literals
|| (name === 'partials.json' && test.name === 'Standalone Indentation')
|| (/\{\{=/).test(test.template)
|| _.any(test.partials, function(partial) { return (/\{\{=/).test(partial); })) {
if (
name === '~lambdas.json' ||
// We also choose to throw if paritals are not found
(name === 'partials.json' && test.name === 'Failed Lookup') ||
// We nest the entire response from partials, not just the literals
(name === 'partials.json' && test.name === 'Standalone Indentation') ||
/\{\{=/.test(test.template) ||
_.any(test.partials, function(partial) {
return /\{\{=/.test(partial);
})
) {
it.skip(name + ' - ' + test.name);
return;
}
@@ -40,9 +41,20 @@ describe('spec', function() {
}
it(name + ' - ' + test.name, function() {
if (test.partials) {
shouldCompileToWithPartials(test.template, [data, {}, test.partials, true], true, test.expected, test.desc + ' "' + test.template + '"');
shouldCompileToWithPartials(
test.template,
[data, {}, test.partials, true],
true,
test.expected,
test.desc + ' "' + test.template + '"'
);
} else {
shouldCompileTo(test.template, [data, {}, {}, true], test.expected, test.desc + ' "' + test.template + '"');
shouldCompileTo(
test.template,
[data, {}, {}, true],
test.expected,
test.desc + ' "' + test.template + '"'
);
}
});
});
+124 -53
View File
@@ -3,43 +3,58 @@ var Exception = Handlebars.Exception;
describe('strict', function() {
describe('strict mode', function() {
it('should error on missing property lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', { strict: true });
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
});
it('should error on missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
equals(template({hello: {bar: 'foo'}}), 'foo');
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({ hello: { bar: 'foo' } }), 'foo');
shouldThrow(function() {
template({hello: {}});
}, Exception, /"bar" not defined in/);
shouldThrow(
function() {
template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
});
it('should handle explicit undefined', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({hello: {bar: undefined}}), '');
equals(template({ hello: { bar: undefined } }), '');
});
it('should error on missing property lookup in known helpers mode', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', {
strict: true,
knownHelpersOnly: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true});
var template = CompilerContext.compile('{{hello}}', { strict: true });
template();
}, Error);
});
it('should error on missing data lookup', function() {
var template = CompilerContext.compile('{{@hello}}', {strict: true});
equals(template(undefined, {data: {hello: 'foo'}}), 'foo');
var template = CompilerContext.compile('{{@hello}}', { strict: true });
equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
shouldThrow(function() {
template();
@@ -47,45 +62,81 @@ describe('strict', function() {
});
it('should not run helperMissing for helper calls', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello foo}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello foo}}', {
strict: true
});
template({foo: true});
}, Exception, /"hello" not defined in/);
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
strict: true
});
template({foo: true});
}, Exception, /"hello" not defined in/);
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
});
it('should throw on ambiguous blocks', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
strict: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
strict: true
});
template({});
}, Exception, /"hello" not defined in/);
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true});
shouldThrow(
function() {
var template = CompilerContext.compile(
'{{#hello.bar}}{{/hello.bar}}',
{ strict: true }
);
template({hello: {}});
}, Exception, /"bar" not defined in/);
template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
});
it('should allow undefined parameters when passed to helpers', function() {
var template = CompilerContext.compile('{{#unless foo}}success{{/unless}}', {strict: true});
var template = CompilerContext.compile(
'{{#unless foo}}success{{/unless}}',
{ strict: true }
);
equals(template({}), 'success');
});
it('should allow undefined hash when passed to helpers', function() {
var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
var template = CompilerContext.compile('{{helper value=@foo}}', {
strict: true
});
var helpers = {
helper: function(options) {
equals('value' in options.hash, true);
@@ -93,19 +144,27 @@ describe('strict', function() {
return 'success';
}
};
equals(template({}, {helpers: helpers}), 'success');
equals(template({}, { helpers: helpers }), 'success');
});
it('should show error location on missing property lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
template({});
}, Exception, '"hello" not defined in [object Object] - 4:5');
shouldThrow(
function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
},
Exception,
'"hello" not defined in [object Object] - 4:5'
);
});
it('should error contains correct location properties on missing property lookup', function() {
try {
var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
} catch (error) {
equals(error.lineNumber, 4);
@@ -118,25 +177,33 @@ describe('strict', function() {
describe('assume objects', function() {
it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
equal(template({}), '');
});
it('should ignore missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
equal(template({hello: {}}), '');
equal(template({ hello: {} }), '');
});
it('should error on missing object', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
template({});
}, Error);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
template();
}, Error);
@@ -144,14 +211,18 @@ describe('strict', function() {
it('should error on missing data lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true});
var template = CompilerContext.compile('{{@hello.bar}}', {
assumeObjects: true
});
template();
}, Error);
});
it('should execute blockHelperMissing', function() {
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {assumeObjects: true});
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
assumeObjects: true
});
equals(template({}), 'foo');
});
+108 -38
View File
@@ -1,6 +1,8 @@
describe('string params mode', function() {
it('arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
stringParams: true
});
var helpers = {
wycats: function(passiveVoice, noun) {
@@ -8,57 +10,99 @@ describe('string params mode', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'HELP ME MY BOSS is.a slave.driver', 'String parameters output');
equals(
result,
'HELP ME MY BOSS is.a slave.driver',
'String parameters output'
);
});
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
var template = CompilerContext.compile(
'{{#wycats is.a slave.driver}}help :({{/wycats}}',
{ stringParams: true }
);
var helpers = {
wycats: function(passiveVoice, noun, options) {
return 'HELP ME MY BOSS ' + passiveVoice + ' ' +
noun + ': ' + options.fn(this);
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
': ' +
options.fn(this)
);
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'HELP ME MY BOSS is.a slave.driver: help :(', 'String parameters output');
equals(
result,
'HELP ME MY BOSS is.a slave.driver: help :(',
'String parameters output'
);
});
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
var template = CompilerContext.compile(
'{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, options) {
return 'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] + ' ' + noun;
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun
);
},
'with': function(context, options) {
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
var result = template(
{
dale: {},
need: 'need-a'
}, {helpers: helpers});
need: 'need-a'
},
{ helpers: helpers }
);
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', 'Proper context variable output');
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
'Proper context variable output'
);
});
it('information about the types is passed along', function() {
var template = CompilerContext.compile("{{tomdale 'need' dad.joke true false}}", { stringParams: true });
var template = CompilerContext.compile(
"{{tomdale 'need' dad.joke true false}}",
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, trueBool, falseBool, options) {
equal(options.types[0], 'StringLiteral', 'the string type is passed');
equal(options.types[1], 'PathExpression', 'the expression type is passed');
equal(options.types[2], 'BooleanLiteral', 'the expression type is passed');
equal(
options.types[1],
'PathExpression',
'the expression type is passed'
);
equal(
options.types[2],
'BooleanLiteral',
'the expression type is passed'
);
equal(desire, 'need', 'the string form is passed for strings');
equal(noun, 'dad.joke', 'the string form is passed for expressions');
equal(trueBool, true, 'raw booleans are passed through');
@@ -72,7 +116,10 @@ describe('string params mode', function() {
});
it('hash parameters get type information', function() {
var template = CompilerContext.compile("{{tomdale he.says desire='need' noun=dad.joke bool=true}}", { stringParams: true });
var template = CompilerContext.compile(
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
{ stringParams: true }
);
var helpers = {
tomdale: function(exclamation, options) {
@@ -94,9 +141,12 @@ describe('string params mode', function() {
});
it('hash parameters get context information', function() {
var template = CompilerContext.compile("{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}", { stringParams: true });
var template = CompilerContext.compile(
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
{ stringParams: true }
);
var context = {dale: {}};
var context = { dale: {} };
var helpers = {
tomdale: function(exclamation, options) {
@@ -110,7 +160,7 @@ describe('string params mode', function() {
equal(options.hash.bool, true);
return 'Helper called';
},
'with': function(withContext, options) {
with: function(withContext, options) {
return options.fn(options.contexts[0][withContext]);
}
};
@@ -120,34 +170,52 @@ describe('string params mode', function() {
});
it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
var template = CompilerContext.compile(
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, options) {
return 'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] + ' ' + noun + ' ' +
options.fn(this);
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun +
' ' +
options.fn(this)
);
},
'with': function(context, options) {
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
var result = template(
{
dale: {},
need: 'need-a'
}, {helpers: helpers});
need: 'need-a'
},
{ helpers: helpers }
);
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', 'Proper context variable output');
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
'Proper context variable output'
);
});
it('with nested block ambiguous', function() {
var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
var template = CompilerContext.compile(
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
{ stringParams: true }
);
var helpers = {
'with': function() {
with: function() {
return 'WITH';
},
view: function() {
@@ -155,12 +223,14 @@ describe('string params mode', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'WITH');
});
it('should handle DATA', function() {
var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true });
var template = CompilerContext.compile('{{foo @bar}}', {
stringParams: true
});
var helpers = {
foo: function(bar, options) {
+75 -21
View File
@@ -31,7 +31,7 @@ describe('subexpressions', function() {
it('mixed paths and helpers', function() {
var string = '{{blog baz.bat (equal a b) baz.bar}}';
var context = { bar: 'LOL', baz: {bat: 'foo!', bar: 'bar!'} };
var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
var helpers = {
blog: function(val, that, theOther) {
return 'val is ' + val + ', ' + that + ' and ' + theOther;
@@ -59,7 +59,7 @@ describe('subexpressions', function() {
});
it('GH-800 : Complex subexpressions', function() {
var context = {a: 'a', b: 'b', c: {c: 'c'}, d: 'd', e: {e: 'e'}};
var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } };
var helpers = {
dash: function(a, b) {
return a + '-' + b;
@@ -69,7 +69,11 @@ describe('subexpressions', function() {
}
};
shouldCompileTo("{{dash 'abc' (concat a b)}}", [context, helpers], 'abc-ab');
shouldCompileTo(
"{{dash 'abc' (concat a b)}}",
[context, helpers],
'abc-ab'
);
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
@@ -122,24 +126,36 @@ describe('subexpressions', function() {
});
it('multiple subexpressions in a hash', function() {
var string = '{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
var string =
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
var helpers = {
input: function(options) {
var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
return new Handlebars.SafeString(
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(string, [{}, helpers], '<input aria-label="Name" placeholder="Example User" />');
shouldCompileTo(
string,
[{}, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
});
it('multiple subexpressions in a hash with context', function() {
var string = '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var string =
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var context = {
item: {
@@ -153,44 +169,82 @@ describe('subexpressions', function() {
var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
return new Handlebars.SafeString(
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(string, [context, helpers], '<input aria-label="Name" placeholder="Example User" />');
shouldCompileTo(
string,
[context, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
});
it('in string params mode,', function() {
var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
var template = CompilerContext.compile(
'{{snog (blorg foo x=y) yeah a=b}}',
{ stringParams: true }
);
var helpers = {
snog: function(a, b, options) {
equals(a, 'foo');
equals(options.types.length, 2, 'string params for outer helper processed correctly');
equals(options.types[0], 'SubExpression', 'string params for outer helper processed correctly');
equals(options.types[1], 'PathExpression', 'string params for outer helper processed correctly');
equals(
options.types.length,
2,
'string params for outer helper processed correctly'
);
equals(
options.types[0],
'SubExpression',
'string params for outer helper processed correctly'
);
equals(
options.types[1],
'PathExpression',
'string params for outer helper processed correctly'
);
return a + b;
},
blorg: function(a, options) {
equals(options.types.length, 1, 'string params for inner helper processed correctly');
equals(options.types[0], 'PathExpression', 'string params for inner helper processed correctly');
equals(
options.types.length,
1,
'string params for inner helper processed correctly'
);
equals(
options.types[0],
'PathExpression',
'string params for inner helper processed correctly'
);
return a;
}
};
var result = template({
foo: {},
yeah: {}
}, {helpers: helpers});
var result = template(
{
foo: {},
yeah: {}
},
{ helpers: helpers }
);
equals(result, 'fooyeah');
});
it('as hashes in string params mode', function() {
var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
var template = CompilerContext.compile('{{blog fun=(bork)}}', {
stringParams: true
});
var helpers = {
blog: function(options) {
@@ -202,7 +256,7 @@ describe('subexpressions', function() {
}
};
var result = template({}, {helpers: helpers});
var result = template({}, { helpers: helpers });
equals(result, 'val is BORK');
});
+395 -48
View File
@@ -15,18 +15,18 @@ describe('Tokenizer', function() {
function tokenize(template) {
var parser = Handlebars.Parser,
lexer = parser.lexer;
lexer = parser.lexer;
lexer.setInput(template);
var out = [],
token;
token;
while ((token = lexer.lex())) {
var result = parser.terminals_[token] || token;
if (!result || result === 'EOF' || result === 'INVALID') {
break;
}
out.push({name: result, text: lexer.yytext});
out.push({ name: result, text: lexer.yytext });
}
return out;
@@ -55,7 +55,16 @@ describe('Tokenizer', function() {
it('supports escaping delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -63,7 +72,14 @@ describe('Tokenizer', function() {
it('supports escaping multiple delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
@@ -72,14 +88,35 @@ describe('Tokenizer', function() {
it('supports escaping a triple stash', function() {
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
});
it('supports escaping escape character', function() {
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -87,7 +124,19 @@ describe('Tokenizer', function() {
it('supports escaping multiple escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -97,7 +146,18 @@ describe('Tokenizer', function() {
it('supports escaped mustaches after escaped escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[4], 'OPEN', '{{');
@@ -108,7 +168,17 @@ describe('Tokenizer', function() {
it('supports escaped escape characters after escaped mustaches', function() {
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
shouldBeToken(result[5], 'CONTENT', '\\');
@@ -118,7 +188,19 @@ describe('Tokenizer', function() {
it('supports escaped escape character on a triple stash', function() {
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN_UNESCAPED',
'ID',
'CLOSE_UNESCAPED',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
@@ -133,7 +215,15 @@ describe('Tokenizer', function() {
var result = tokenize('{{foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows path literals with []', function() {
@@ -143,7 +233,18 @@ describe('Tokenizer', function() {
it('allows multiple path literals on a line with []', function() {
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE',
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows escaped literals in []', function() {
@@ -158,13 +259,29 @@ describe('Tokenizer', function() {
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
var result = tokenize('{{../foo/bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
it('tokenizes a path with .. as a parent path', function() {
var result = tokenize('{{../foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
@@ -216,7 +333,15 @@ describe('Tokenizer', function() {
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo/bar.baz }}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_PARTIAL',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('tokenizes partial block declarations', function() {
@@ -225,34 +350,69 @@ describe('Tokenizer', function() {
});
it('tokenizes a comment as "COMMENT"', function() {
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
});
it('tokenizes a block comment as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
});
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}');
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
var result = tokenize(
'foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'
);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
});
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
var result = tokenize('{{#foo}}content{{/foo}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
});
it('tokenizes directives', function() {
shouldMatchTokens(
tokenize('{{#*foo}}content{{/foo}}'),
['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
shouldMatchTokens(
tokenize('{{*foo}}'),
['OPEN', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes inverse sections as "INVERSE"', function() {
@@ -351,28 +511,98 @@ describe('Tokenizer', function() {
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize('{{ foo bar baz=bat }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'NUMBER',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=true }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=false }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar\n baz=bat }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" bam=wot }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{foo omg bar=baz bat="bam"}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
shouldBeToken(result[2], 'ID', 'omg');
});
@@ -386,7 +616,15 @@ describe('Tokenizer', function() {
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{ foo bar=@baz }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'EQUALS',
'DATA',
'ID',
'CLOSE'
]);
shouldBeToken(result[5], 'ID', 'baz');
});
@@ -400,12 +638,27 @@ describe('Tokenizer', function() {
it('tokenizes subexpressions', function() {
var result = tokenize('{{foo (bar)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{foo (a-x b-y)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'a-x');
shouldBeToken(result[4], 'ID', 'b-y');
@@ -413,7 +666,21 @@ describe('Tokenizer', function() {
it('tokenizes nested subexpressions', function() {
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[3], 'ID', 'bar');
shouldBeToken(result[5], 'ID', 'lol');
shouldBeToken(result[6], 'ID', 'rofl');
@@ -421,29 +688,109 @@ describe('Tokenizer', function() {
});
it('tokenizes nested subexpressions: literals', function() {
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
var result = tokenize(
'{{foo (bar (lol true) false) (baz 1) (blah \'b\') (blorg "c")}}'
);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'BOOLEAN',
'CLOSE_SEXPR',
'BOOLEAN',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'NUMBER',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'CLOSE'
]);
});
it('tokenizes block params', function() {
var result = tokenize('{{#foo as |bar|}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as |bar baz|}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as | bar baz |}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as as | bar baz |}}');
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{else foo as |bar baz|}}');
shouldMatchTokens(result, ['OPEN_INVERSE_CHAIN', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
shouldMatchTokens(result, [
'OPEN_INVERSE_CHAIN',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
});
it('tokenizes raw blocks', function() {
var result = tokenize('{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}');
shouldMatchTokens(result, ['OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK', 'CONTENT', 'OPEN_RAW_BLOCK', 'ID', 'CLOSE_RAW_BLOCK', 'CONTENT', 'END_RAW_BLOCK']);
var result = tokenize(
'{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'
);
shouldMatchTokens(result, [
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK',
'CONTENT',
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK'
]);
});
});
+244 -54
View File
@@ -1,7 +1,7 @@
describe('track ids', function() {
var context;
beforeEach(function() {
context = {is: {a: 'foo'}, slave: {driver: 'bar'}};
context = { is: { a: 'foo' }, slave: { driver: 'bar' } };
});
it('should not include anything without the flag', function() {
@@ -16,38 +16,70 @@ describe('track ids', function() {
}
};
equals(template({}, {helpers: helpers}), 'success');
equals(template({}, { helpers: helpers }), 'success');
});
it('should include argument ids', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {trackIds: true});
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], 'is.a');
equal(options.ids[1], 'slave.driver');
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo slave.driver:bar'
);
});
it('should include hash ids', function() {
var template = CompilerContext.compile('{{wycats bat=is.a baz=slave.driver}}', {trackIds: true});
var template = CompilerContext.compile(
'{{wycats bat=is.a baz=slave.driver}}',
{ trackIds: true }
);
var helpers = {
wycats: function(options) {
equal(options.hashIds.bat, 'is.a');
equal(options.hashIds.baz, 'slave.driver');
return 'HELP ME MY BOSS ' + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz;
return (
'HELP ME MY BOSS ' +
options.hashIds.bat +
':' +
options.hash.bat +
' ' +
options.hashIds.baz +
':' +
options.hash.baz
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo slave.driver:bar'
);
});
it('should note ../ and ./ references', function() {
var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver this.is.a this}}', {trackIds: true});
var template = CompilerContext.compile(
'{{wycats ./is.a ../slave.driver this.is.a this}}',
{ trackIds: true }
);
var helpers = {
wycats: function(passiveVoice, noun, thiz, thiz2, options) {
@@ -56,29 +88,57 @@ describe('track ids', function() {
equal(options.ids[2], 'is.a');
equal(options.ids[3], '');
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo ../slave.driver:undefined'
);
});
it('should note @data references', function() {
var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {trackIds: true});
var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], '@is.a');
equal(options.ids[1], '@slave.driver');
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(template({}, {helpers: helpers, data: context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar');
equals(
template({}, { helpers: helpers, data: context }),
'HELP ME MY BOSS @is.a:foo @slave.driver:bar'
);
});
it('should return null for constants', function() {
var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {trackIds: true});
var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
@@ -86,17 +146,31 @@ describe('track ids', function() {
equal(options.ids[1], null);
equal(options.hashIds.key, null);
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun + ' ' + options.hash.key;
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
' ' +
options.hash.key
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1 foo false');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS 1 foo false'
);
});
it('should return true for subexpressions', function() {
var template = CompilerContext.compile('{{wycats (sub)}}', {trackIds: true});
var template = CompilerContext.compile('{{wycats (sub)}}', {
trackIds: true
});
var helpers = {
sub: function() { return 1; },
sub: function() {
return 1;
},
wycats: function(passiveVoice, options) {
equal(options.ids[0], true);
@@ -104,28 +178,43 @@ describe('track ids', function() {
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1');
});
it('should use block param paths', function() {
var template = CompilerContext.compile('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}',
{ trackIds: true }
);
var helpers = {
doIt: function(options) {
var blockParams = [this.is];
blockParams.path = ['zomg'];
return options.fn(this, {blockParams: blockParams});
return options.fn(this, { blockParams: blockParams });
},
wycats: function(passiveVoice, noun, blah, options) {
equal(options.ids[0], 'zomg.a');
equal(options.ids[1], 'slave.driver');
equal(options.ids[2], 'zomg');
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS zomg.a:foo slave.driver:bar');
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS zomg.a:foo slave.driver:bar'
);
});
describe('builtin helpers', function() {
@@ -140,53 +229,119 @@ describe('track ids', function() {
describe('#each', function() {
it('should track contextPath for arrays', function() {
var template = CompilerContext.compile('{{#each array}}{{wycats name}}{{/each}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#each array}}{{wycats name}}{{/each}}',
{ trackIds: true }
);
equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0\nbar:array.1\n');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:array.0\nbar:array.1\n'
);
});
it('should track contextPath for keys', function() {
var template = CompilerContext.compile('{{#each object}}{{wycats name}}{{/each}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#each object}}{{wycats name}}{{/each}}',
{ trackIds: true }
);
equals(template({object: {foo: {name: 'foo'}, bar: {name: 'bar'}}}, {helpers: helpers}), 'foo:object.foo\nbar:object.bar\n');
equals(
template(
{ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } },
{ helpers: helpers }
),
'foo:object.foo\nbar:object.bar\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}',
{ trackIds: true }
);
equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:.array..0\nbar:.array..1\n'
);
});
it('should handle block params', function() {
var template = CompilerContext.compile('{{#each array as |value|}}{{blockParams value.name}}{{/each}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#each array as |value|}}{{blockParams value.name}}{{/each}}',
{ trackIds: true }
);
equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0.name\nbar:array.1.name\n');
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:array.0.name\nbar:array.1.name\n'
);
});
});
describe('#with', function() {
it('should track contextPath', function() {
var template = CompilerContext.compile('{{#with field}}{{wycats name}}{{/with}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#with field}}{{wycats name}}{{/with}}',
{ trackIds: true }
);
equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}',
{ trackIds: true }
);
equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n');
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
describe('#blockHelperMissing', function() {
it('should track contextPath for arrays', function() {
var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n');
equals(
template({ field: [{ name: 'foo' }] }, { helpers: helpers }),
'foo:field.0\n'
);
});
it('should track contextPath for keys', function() {
var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n');
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true});
var template = CompilerContext.compile(
'{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}',
{ trackIds: true }
);
equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n');
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
});
@@ -202,36 +357,71 @@ describe('track ids', function() {
};
it('should pass track id for basic partial', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude}}{{/dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n'
);
});
it('should pass track id for context partial', function() {
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {
trackIds: true
}),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{#each this}}{{wycats name}}{{/each}}', {trackIds: true})
dude: CompilerContext.compile(
'{{#each this}}{{wycats name}}{{/each}}',
{ trackIds: true }
)
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n'
);
});
it('should invalidate context for partials with parameters', function() {
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}', {trackIds: true}),
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:true\nAlan:true\n');
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:true\nAlan:true\n'
);
});
});
});
+16 -5
View File
@@ -5,19 +5,30 @@ describe('utils', function() {
if (!(safe instanceof Handlebars.SafeString)) {
throw new Error('Must be instance of SafeString');
}
equals(safe.toString(), 'testing 1, 2, 3', 'SafeString is equivalent to its underlying string');
equals(
safe.toString(),
'testing 1, 2, 3',
'SafeString is equivalent to its underlying string'
);
});
it('it should not escape SafeString properties', function() {
var name = new Handlebars.SafeString('<em>Sean O&#x27;Malley</em>');
shouldCompileTo('{{name}}', [{name: name}], '<em>Sean O&#x27;Malley</em>');
shouldCompileTo(
'{{name}}',
[{ name: name }],
'<em>Sean O&#x27;Malley</em>'
);
});
});
describe('#escapeExpression', function() {
it('shouhld escape html', function() {
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo&lt;&amp;&quot;&#x27;&gt;');
equals(
Handlebars.Utils.escapeExpression('foo<&"\'>'),
'foo&lt;&amp;&quot;&#x27;&gt;'
);
equals(Handlebars.Utils.escapeExpression('foo='), 'foo&#x3D;');
});
it('should not escape SafeString', function() {
@@ -58,7 +69,7 @@ describe('utils', function() {
equals(Handlebars.Utils.isEmpty(0), false);
equals(Handlebars.Utils.isEmpty([1]), false);
equals(Handlebars.Utils.isEmpty('foo'), false);
equals(Handlebars.Utils.isEmpty({bar: 1}), false);
equals(Handlebars.Utils.isEmpty({ bar: 1 }), false);
});
});
@@ -69,7 +80,7 @@ describe('utils', function() {
}
A.prototype.b = 4;
var b = {b: 2};
var b = { b: 2 };
Handlebars.Utils.extend(b, new A());
+46 -24
View File
@@ -7,7 +7,11 @@ describe('Visitor', function() {
// Simply run the thing and make sure it does not fail and that all of the
// stub methods are executed
var visitor = new Handlebars.Visitor();
visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'));
visitor.accept(
Handlebars.parse(
'{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'
)
);
visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{* bar }}'));
@@ -40,7 +44,11 @@ describe('Visitor', function() {
equal(comment.value, 'comment');
};
visitor.accept(Handlebars.parse('{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'));
visitor.accept(
Handlebars.parse(
'{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'
)
);
});
describe('mutating', function() {
@@ -50,12 +58,15 @@ describe('Visitor', function() {
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return {type: 'NumberLiteral', value: 42, loc: string.loc};
return { type: 'NumberLiteral', value: 42, loc: string.loc };
};
var ast = Handlebars.parse('{{foo foo="foo"}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should treat undefined resonse as identity', function() {
var visitor = new Handlebars.Visitor();
@@ -63,7 +74,10 @@ describe('Visitor', function() {
var ast = Handlebars.parse('{{foo foo=42}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should remove false responses', function() {
var visitor = new Handlebars.Visitor();
@@ -78,30 +92,38 @@ describe('Visitor', function() {
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
});
it('should throw when removing required values', function() {
shouldThrow(function() {
var visitor = new Handlebars.Visitor();
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return false;
};
visitor.mutating = true;
visitor.PathExpression = function() {
return false;
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
}, Handlebars.Exception, 'MustacheStatement requires path');
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'MustacheStatement requires path'
);
});
it('should throw when returning non-node responses', function() {
shouldThrow(function() {
var visitor = new Handlebars.Visitor();
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return {};
};
visitor.mutating = true;
visitor.PathExpression = function() {
return {};
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
}, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting path on MustacheStatement');
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'Unexpected node type "undefined" found when accepting path on MustacheStatement'
);
});
});
describe('arrays', function() {
@@ -110,7 +132,7 @@ describe('Visitor', function() {
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return {type: 'NumberLiteral', value: 42, loc: string.locInfo};
return { type: 'NumberLiteral', value: 42, loc: string.locInfo };
};
var ast = Handlebars.parse('{{foo "foo"}}');
+64 -15
View File
@@ -1,6 +1,6 @@
describe('whitespace control', function() {
it('should strip whitespace around mustache calls', function() {
var hash = {foo: 'bar<'};
var hash = { foo: 'bar<' };
shouldCompileTo(' {{~foo~}} ', hash, 'bar&lt;');
shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
@@ -14,15 +14,23 @@ describe('whitespace control', function() {
describe('blocks', function() {
it('should strip whitespace around simple block calls', function() {
var hash = {foo: 'bar<'};
var hash = { foo: 'bar<' };
shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
shouldCompileTo(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
shouldCompileTo(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', hash, ' abara ');
shouldCompileTo(
' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
hash,
'bar'
);
shouldCompileTo(
' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ',
hash,
' abara '
);
});
it('should strip whitespace around inverse block calls', function() {
var hash = {};
@@ -32,10 +40,14 @@ describe('whitespace control', function() {
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
shouldCompileTo(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
shouldCompileTo(
' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
hash,
'bar'
);
});
it('should strip whitespace around complex block calls', function() {
var hash = {foo: 'bar<'};
var hash = { foo: 'bar<' };
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
@@ -44,8 +56,16 @@ describe('whitespace control', function() {
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar');
shouldCompileTo('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar<');
shouldCompileTo(
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'bar'
);
shouldCompileTo(
'\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'bar<'
);
hash = {};
@@ -56,21 +76,50 @@ describe('whitespace control', function() {
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'baz');
shouldCompileTo(
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'baz'
);
});
});
it('should strip whitespace around partials', function() {
shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar');
shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar ');
shouldCompileToWithPartials(
'foo {{~> dude~}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo {{> dude~}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo bar'
);
shouldCompileToWithPartials(
'foo {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo bar '
);
shouldCompileToWithPartials('foo\n {{~> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
shouldCompileToWithPartials('foo\n {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo\n bar');
shouldCompileToWithPartials(
'foo\n {{~> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo\n {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo\n bar'
);
});
it('should only strip whitespace once', function() {
var hash = {foo: 'bar'};
var hash = { foo: 'bar' };
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
});
+9 -6
View File
@@ -1,14 +1,16 @@
var _ = require('underscore'),
async = require('neo-async'),
metrics = require('../bench');
async = require('neo-async'),
metrics = require('../bench');
module.exports = function(grunt) {
grunt.registerTask('metrics', function() {
var done = this.async(),
execName = grunt.option('name'),
events = {};
execName = grunt.option('name'),
events = {};
async.each(_.keys(metrics), function(name, complete) {
async.each(
_.keys(metrics),
function(name, complete) {
if (/^_/.test(name) || (execName && name !== execName)) {
return complete();
}
@@ -18,6 +20,7 @@ module.exports = function(grunt) {
complete();
});
},
done);
done
);
});
};
+13 -3
View File
@@ -7,10 +7,14 @@ module.exports = function(grunt) {
var cmd = './node_modules/.bin/jison';
if (process.platform === 'win32') {
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) {
if (code != 0) {
grunt.fatal('Jison failure: ' + code);
@@ -18,7 +22,13 @@ module.exports = function(grunt) {
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.write('lib/handlebars/compiler/parser.js', src);
+31 -16
View File
@@ -1,8 +1,8 @@
var _ = require('underscore'),
async = require('neo-async'),
AWS = require('aws-sdk'),
git = require('./util/git'),
semver = require('semver');
async = require('neo-async'),
AWS = require('aws-sdk'),
git = require('./util/git'),
semver = require('semver');
module.exports = function(grunt) {
grunt.registerTask('publish:latest', function() {
@@ -53,21 +53,27 @@ module.exports = function(grunt) {
function initSDK() {
var bucket = process.env.S3_BUCKET_NAME,
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
if (!bucket || !key || !secret) {
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) {
var s3 = new AWS.S3(),
bucket = process.env.S3_BUCKET_NAME;
bucket = process.env.S3_BUCKET_NAME;
async.each(_.keys(files), function(file, callback) {
var params = {Bucket: bucket, Key: file, Body: grunt.file.read(files[file])};
async.each(
_.keys(files),
function(file, callback) {
var params = {
Bucket: bucket,
Key: file,
Body: grunt.file.read(files[file])
};
s3.putObject(params, function(err) {
if (err) {
throw err;
@@ -77,15 +83,24 @@ module.exports = function(grunt) {
}
});
},
callback);
callback
);
}
function fileMap(suffixes) {
var map = {};
_.each(['handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', 'handlebars.runtime.min.js'], function(file) {
_.each(suffixes, function(suffix) {
map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file;
});
});
_.each(
[
'handlebars.js',
'handlebars.min.js',
'handlebars.runtime.js',
'handlebars.runtime.min.js'
],
function(file) {
_.each(suffixes, function(suffix) {
map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file;
});
}
);
return map;
}
};
+39 -9
View File
@@ -1,13 +1,13 @@
var childProcess = require('child_process'),
fs = require('fs'),
os = require('os');
fs = require('fs'),
os = require('os');
module.exports = function(grunt) {
grunt.registerTask('test:bin', function() {
var done = this.async();
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
if (os.platform() === 'win32') {
@@ -19,10 +19,19 @@ module.exports = function(grunt) {
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) {
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();
@@ -31,7 +40,9 @@ module.exports = function(grunt) {
grunt.registerTask('test:mocha', function() {
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) {
if (code !== 0) {
grunt.fatal(code + ' tests failed');
@@ -42,7 +53,11 @@ module.exports = function(grunt) {
grunt.registerTask('test:cov', function() {
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) {
if (code != 0) {
grunt.fatal(code + ' tests failed');
@@ -53,7 +68,9 @@ module.exports = function(grunt) {
grunt.registerTask('test:min', function() {
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) {
if (code !== 0) {
grunt.fatal(code + ' tests failed');
@@ -65,7 +82,20 @@ module.exports = function(grunt) {
grunt.registerTask('test:check-cov', function() {
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) {
if (code != 0) {
grunt.fatal('Coverage check failed: ' + code);
+13 -5
View File
@@ -17,7 +17,10 @@ module.exports = {
});
},
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);
});
},
@@ -47,10 +50,13 @@ module.exports = {
});
},
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
// 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);
}
@@ -77,7 +83,9 @@ module.exports = {
});
},
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) {
throw new Error('git.tag: ' + err.message);
}
@@ -98,7 +106,7 @@ module.exports = {
});
var versionTags = tags.filter(function(info) {
return (/^v/.test(info[0]));
return /^v/.test(info[0]);
});
callback(undefined, versionTags[0] || tags[0]);
+54 -28
View File
@@ -1,38 +1,64 @@
var async = require('neo-async'),
git = require('./util/git'),
semver = require('semver');
git = require('./util/git'),
semver = require('semver');
module.exports = function(grunt) {
grunt.registerTask('version', 'Updates the current release version', function() {
var done = this.async(),
grunt.registerTask(
'version',
'Updates the current release version',
function() {
var done = this.async(),
pkg = grunt.config('pkg'),
version = grunt.option('ver');
if (!semver.valid(version)) {
throw new Error('Must provide a version number (Ex: --ver=1.0.0):\n\t' + version + '\n\n');
if (!semver.valid(version)) {
throw new Error(
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
version +
'\n\n'
);
}
pkg.version = version;
grunt.config('pkg', pkg);
grunt.log.writeln('Updating to version ' + version);
async.each(
[
[
'lib/handlebars/base.js',
/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) {
replace.apply(undefined, args);
grunt.log.writeln(' - ' + args[0]);
git.add(args[0], callback);
},
function() {
grunt.task.run(['default']);
done();
}
);
}
pkg.version = version;
grunt.config('pkg', pkg);
grunt.log.writeln('Updating to version ' + version);
async.each([
['lib/handlebars/base.js', (/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) {
replace.apply(undefined, args);
grunt.log.writeln(' - ' + args[0]);
git.add(args[0], callback);
},
function() {
grunt.task.run(['default']);
done();
});
});
);
function replace(path, regex, value) {
var content = grunt.file.read(path);