style: reformat all files using prettier
This commit is contained in:
+98
-48
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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}}'
|
||||
};
|
||||
|
||||
@@ -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}}',
|
||||
|
||||
@@ -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}}'
|
||||
};
|
||||
|
||||
@@ -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' }
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -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}}'
|
||||
};
|
||||
|
||||
@@ -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 %>'
|
||||
|
||||
@@ -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 %>'
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}}' }
|
||||
|
||||
@@ -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}}',
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3,4 +3,3 @@ import registerInline from './decorators/inline';
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {extend} from '../utils';
|
||||
import { extend } from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 + '"'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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,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
@@ -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
@@ -1,4 +1,3 @@
|
||||
|
||||
const escape = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
@@ -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
@@ -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
@@ -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
@@ -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: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
'by default expressions should be escaped');
|
||||
shouldCompileTo(
|
||||
'{{awesome}}',
|
||||
{ awesome: '&"\'`\\<>' },
|
||||
'&"'`\\<>',
|
||||
'by default expressions should be escaped'
|
||||
);
|
||||
|
||||
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: <b>'}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
'escaping should properly handle amperstands');
|
||||
shouldCompileTo(
|
||||
'{{awesome}}',
|
||||
{ awesome: 'Escaped, <b> looks like: <b>' },
|
||||
'Escaped, <b> looks like: &lt;b&gt;',
|
||||
'escaping should properly handle amperstands'
|
||||
);
|
||||
});
|
||||
|
||||
it("functions returning safestrings shouldn't be escaped", function() {
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
|
||||
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
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
@@ -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
@@ -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 = '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||
var expected2 = '2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||
var expected1 =
|
||||
'<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||
var expected2 =
|
||||
'2. GOODBYE! <b>#1</b>. 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
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+5
-3
@@ -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 = {
|
||||
|
||||
Vendored
+48
-18
@@ -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;
|
||||
|
||||
|
||||
Vendored
-1
@@ -3,7 +3,6 @@ require('./common');
|
||||
var chai = require('chai');
|
||||
var dirtyChai = require('dirty-chai');
|
||||
|
||||
|
||||
chai.use(dirtyChai);
|
||||
global.expect = chai.expect;
|
||||
|
||||
|
||||
Vendored
+12
-8
@@ -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');
|
||||
|
||||
Vendored
+10
-4
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'Malley</em>');
|
||||
|
||||
shouldCompileTo('{{name}}', [{name: name}], '<em>Sean O'Malley</em>');
|
||||
shouldCompileTo(
|
||||
'{{name}}',
|
||||
[{ name: name }],
|
||||
'<em>Sean O'Malley</em>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#escapeExpression', function() {
|
||||
it('shouhld escape html', function() {
|
||||
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>');
|
||||
equals(
|
||||
Handlebars.Utils.escapeExpression('foo<&"\'>'),
|
||||
'foo<&"'>'
|
||||
);
|
||||
equals(Handlebars.Utils.escapeExpression('foo='), 'foo=');
|
||||
});
|
||||
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
@@ -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
@@ -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<');
|
||||
shouldCompileTo(' {{~foo}} ', hash, 'bar< ');
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user