Compare commits

..

6 Commits

Author SHA1 Message Date
Nils Knappmeier 51892c296e some minor refactorings 2019-11-30 16:37:43 +01:00
Nils Knappmeier c5b699db00 fixup: revert wrongly added "['default']", fix amd module name in, npm audit fix 2019-11-26 23:26:49 +01:00
Nils Knappmeier ee4dd7d4bf use babel-plugin-add-module-exports to build compatible commonjs modules
- compatible to older-babel versions with broken default exports
- otherwise "import * as Handlebars from 'handlebars'" will not work.
2019-11-25 23:02:47 +01:00
Nils Knappmeier 4801f649df minor styling fixes in test 2019-11-25 23:01:36 +01:00
Nils Knappmeier 9888569dcb chore: update webpack and babel 2019-11-24 00:03:02 +01:00
Nils Knappmeier 1ade5a0892 chore: remove obsolete json2.js
- it is just required to run tests on
   ie6/7 and other very old browsers
  that are not supported anymore
  by SauceLabs
2019-11-23 07:13:45 +01:00
143 changed files with 14468 additions and 11839 deletions
+1 -24
View File
@@ -1,25 +1,2 @@
.rvmrc dist
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
# JS-Snippets
src/*.js
+103 -50
View File
@@ -1,66 +1,119 @@
module.exports = { module.exports = {
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'], "extends": ["eslint:recommended","plugin:compat/recommended"],
globals: { "globals": {
self: false "self": false,
"SOURCE_MAPS": true
}, },
env: { "env": {
node: true, "node": true,
es6: true "es6": true
}, },
rules: { "rules": {
'no-console': 'warn', // overrides eslint:recommended defaults
"no-sparse-arrays": "off",
"no-func-assign": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-unreachable": "warn",
// Possible Errors //
//-----------------//
"no-unsafe-negation": "error",
// temporarily disabled until the violating places are fixed.
'no-func-assign': 'off',
'no-sparse-arrays': 'off',
// Best Practices // // Best Practices //
//----------------// //----------------//
'default-case': 'warn', "curly": "error",
'guard-for-in': 'warn', "default-case": "warn",
'no-alert': 'error', "dot-notation": ["error", { "allowKeywords": false }],
'no-caller': 'error', "guard-for-in": "warn",
'no-div-regex': 'warn', "no-alert": "error",
'no-eval': 'error', "no-caller": "error",
'no-extend-native': 'error', "no-div-regex": "warn",
'no-extra-bind': 'error', "no-eval": "error",
'no-floating-decimal': 'error', "no-extend-native": "error",
'no-implied-eval': 'error', "no-extra-bind": "error",
'no-iterator': 'error', "no-floating-decimal": "error",
'no-labels': 'error', "no-implied-eval": "error",
'no-lone-blocks': 'error', "no-iterator": "error",
'no-loop-func': 'error', "no-labels": "error",
'no-multi-str': 'warn', "no-lone-blocks": "error",
'no-global-assign': 'error', "no-loop-func": "error",
'no-new': 'error', "no-multi-spaces": "error",
'no-new-func': 'error', "no-multi-str": "warn",
'no-new-wrappers': 'error', "no-global-assign": "error",
'no-octal-escape': 'error', "no-new": "error",
'no-process-env': 'error', "no-new-func": "error",
'no-proto': 'error', "no-new-wrappers": "error",
'no-return-assign': 'error', "no-octal-escape": "error",
'no-script-url': 'error', "no-process-env": "error",
'no-self-compare': 'error', "no-proto": "error",
'no-sequences': 'error', "no-return-assign": "error",
'no-throw-literal': 'error', "no-script-url": "error",
'no-unused-expressions': 'error', "no-self-compare": "error",
'no-warning-comments': 'warn', "no-sequences": "error",
'no-with': 'error', "no-throw-literal": "error",
radix: 'error', "no-unused-expressions": "error",
"no-warning-comments": "warn",
"no-with": "error",
"radix": "error",
"wrap-iife": "error",
"no-prototype-builtins": "error",
// Variables // // Variables //
//-----------// //-----------//
'no-label-var': 'error', "no-catch-shadow": "error",
'no-undef-init': 'error', "no-label-var": "error",
'no-use-before-define': ['error', 'nofunc'], "no-shadow-restricted-names": "error",
"no-undef-init": "error",
"no-use-before-define": ["error", "nofunc"],
// Stylistic Issues //
//------------------//
"comma-dangle": ["error", "never"],
"quote-props": ["error", "as-needed", { "keywords": true, "unnecessary": false }],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "error",
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"consistent-this": ["warn", "self"],
"eol-last": "error",
"func-style": ["error", "declaration"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true
}],
"new-cap": "error",
"new-parens": "error",
"no-array-constructor": "error",
"no-lonely-if": "error",
"no-mixed-spaces-and-tabs": "error",
"no-nested-ternary": "warn",
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-extra-parens": ["error", "functions"],
"quotes": ["error", "single", "avoid-escape"],
"semi": "error",
"semi-spacing": ["error", { "before": false, "after": true }],
"keyword-spacing": "error",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", { "anonymous": "never", "named": "never" }],
"space-in-parens": ["error", "never"],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": ["error", "always", { "markers": [","] }],
"wrap-regex": "warn",
// ECMAScript 6 // // ECMAScript 6 //
//--------------// //--------------//
'no-var': 'error' "no-var": "warn"
}, },
parserOptions: { "parserOptions": {
sourceType: 'module', "sourceType": "module",
ecmaVersion: 6, "ecmaVersion": 6,
ecmaFeatures: {} "ecmaFeatures": {}
} }
}; };
+6 -10
View File
@@ -1,19 +1,15 @@
vendor
.rvmrc .rvmrc
.DS_Store .DS_Store
lib/handlebars/compiler/parser.js
/dist/
/tmp/ /tmp/
/coverage/
node_modules
*.sublime-project *.sublime-project
*.sublime-workspace *.sublime-workspace
npm-debug.log npm-debug.log
sauce_connect.log* sauce_connect.log*
.idea .idea
/yarn-error.log yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz /handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/
+2
View File
@@ -0,0 +1,2 @@
instrumentation:
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
-22
View File
@@ -1,22 +0,0 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
+20 -33
View File
@@ -1,39 +1,26 @@
language: node_js language: node_js
jobs: before_install:
include: - npm install -g grunt-cli
- stage: test script:
name: check javascript (eslint) - grunt --stack travis
node_js: lts/*
script: npm run lint
- stage: test
name: check formatting (prettier)
node_js: lts/*
script: npm run check-format
- stage: test
name: check typescript definitions (dtslint)
node_js: lts/*
script: npm run dtslint
- stage: test
name: extensive tests and publish to aws
script: npm run extensive-tests-and-publish-to-aws
env:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
- SAUCE_USERNAME=handlebars
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
node_js: '10'
- stage: test
name: test with latest nodejs-lts
node_js: lts/*
script: npm run test
- stage: test
name: test with active nodejs
node_js: node
script: npm run test
cache: npm
email: email:
on_failure: change on_failure: change
on_success: never on_success: never
env:
global:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
matrix:
include:
- node_js: '10'
env:
- PUBLISH=true
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
cache:
directories:
- node_modules
git: git:
depth: 100 depth: 100
+9 -31
View File
@@ -12,8 +12,8 @@ Documentation issues on the handlebarsjs.com site should be reported on [handleb
## Branches ## Branches
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch. * The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
- The branch `master` contains the next version. A release date is not yet specified. Maintainers * The branch `master` contains the next version. A release date is not yet specified. Maintainers
should merge the branch `4.x` into the master branch regularly. should merge the branch `4.x` into the master branch regularly.
## Pull Requests ## Pull Requests
@@ -21,7 +21,6 @@ Documentation issues on the handlebarsjs.com site should be reported on [handleb
We also accept [pull requests][pull-request]! We also accept [pull requests][pull-request]!
Generally we like to see pull requests that Generally we like to see pull requests that
- Maintain the existing code style - Maintain the existing code style
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request) - Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
@@ -32,8 +31,8 @@ Generally we like to see pull requests that
To build Handlebars.js you'll need a few things installed. To build Handlebars.js you'll need a few things installed.
- Node.js * Node.js
- [Grunt](http://gruntjs.com/getting-started) * [Grunt](http://gruntjs.com/getting-started)
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`. Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
@@ -52,13 +51,11 @@ If you notice any problems, please report them to the GitHub issue tracker at
##Running Tests ##Running Tests
To run tests locally, first install all dependencies. To run tests locally, first install all dependencies.
```sh ```sh
npm install npm install
``` ```
Clone the mustache specs into the spec/mustache folder. Clone the mustache specs into the spec/mustache folder.
```sh ```sh
cd spec cd spec
rm -r mustache rm -r mustache
@@ -66,29 +63,10 @@ git clone https://github.com/mustache/spec.git mustache
``` ```
From the root directory, run the tests. From the root directory, run the tests.
```sh ```sh
npm test npm test
``` ```
## Linting and Formatting
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
We do linting and formatting in two phases:
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
while warnings are allowed.
- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
are forbidden.
You can use the following scripts to make sure that the travis-job does not fail:
- **npm run lint** will run `eslint` and fail on warnings
- **npm run format** will run `prettier` on all files
- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
## Ember testing ## Ember testing
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script. The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
@@ -105,7 +83,7 @@ npm test
## Releasing the latest version ## Releasing the latest version
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._ *When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch.*
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks. Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
@@ -125,10 +103,10 @@ gem push handlebars-source-*.gem
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version in those places still point to the latest version
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag) * [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
- [The bower package](https://github.com/components/handlebars.js) (check the package.json) * [The bower package](https://github.com/components/handlebars.js) (check the package.json)
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag) * [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
- [RubyGems](https://rubygems.org/gems/handlebars-source) * [RubyGems](https://rubygems.org/gems/handlebars-source)
When everything is OK, the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation. When everything is OK, the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
+67 -173
View File
@@ -1,27 +1,35 @@
/* eslint-disable no-process-env */ /* eslint-disable no-process-env */
module.exports = function(grunt) { module.exports = function(grunt) {
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
clean: [ eslint: {
'tmp', files: [
'dist', '*.js',
'lib/handlebars/compiler/parser.js', 'bench/**/*.js',
'integration-testing/**/node_modules' 'tasks/**/*.js',
], 'lib/**/!(*.min|parser).js',
'spec/**/!(*.amd|json2|require).js',
'integration-testing/multi-nodejs-test/*.js',
'integration-testing/webpack-test/*.js',
'integration-testing/webpack-test/src/*.js'
]
},
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js', 'integration-testing/**/node_modules'],
copy: { copy: {
dist: { dist: {
options: { options: {
processContent: function(content) { processContent: function(content) {
return ( return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
grunt.template.process( + content;
'/**!\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: { cdnjs: {
files: [ files: [
@@ -30,122 +38,45 @@ module.exports = function(grunt) {
}, },
components: { components: {
files: [ files: [
{ {expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
expand: true,
cwd: 'components/',
src: ['**'],
dest: 'dist/components'
},
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'} {expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
] ]
} }
}, },
webpack: {
build: require('./webpack.config')
},
babel: { babel: {
options: { options: {
sourceMaps: 'inline', sourceMaps: 'inline',
loose: ['es6.modules'],
auxiliaryCommentBefore: 'istanbul ignore next' auxiliaryCommentBefore: 'istanbul ignore next'
}, },
amd: { amd: {
options: { options: {
modules: 'amd' plugins: ['@babel/plugin-transform-modules-amd']
}, },
files: [ files: [{
{
expand: true, expand: true,
cwd: 'lib/', cwd: 'lib/',
src: '**/!(index).js', src: '**/!(index).js',
dest: 'dist/amd/' dest: 'dist/amd/'
} }]
]
}, },
cjs: { cjs: {
options: { options: {
modules: 'common' plugins: [
'@babel/plugin-transform-modules-commonjs',
// support "import * as Handlebars" for backward-compatibility
'babel-plugin-add-module-exports'
]
}, },
files: [ files: [{
{
cwd: 'lib/', cwd: 'lib/',
expand: true, expand: true,
src: '**/!(index).js', src: '**/!(index).js',
dest: 'dist/cjs/' dest: 'dist/cjs/'
} }]
]
}
},
webpack: {
options: {
context: __dirname,
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'
}
]
},
output: {
path: 'dist/',
library: 'Handlebars',
libraryTarget: 'umd'
}
},
handlebars: {
entry: './lib/handlebars.js',
output: {
filename: 'handlebars.js'
}
},
runtime: {
entry: './lib/handlebars.runtime.js',
output: {
filename: 'handlebars.runtime.js'
}
}
},
requirejs: {
options: {
optimize: 'none',
baseUrl: 'dist/amd/'
},
dist: {
options: {
name: 'handlebars',
out: 'dist/handlebars.amd.js'
}
},
runtime: {
options: {
name: 'handlebars.runtime',
out: 'dist/handlebars.runtime.amd.js'
}
}
},
uglify: {
options: {
mangle: true,
compress: true,
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');
}
}
]
} }
}, },
@@ -169,10 +100,7 @@ module.exports = function(grunt) {
all: { all: {
options: { options: {
build: process.env.TRAVIS_JOB_ID, build: process.env.TRAVIS_JOB_ID,
urls: [ urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
'http://localhost:9999/spec/?headless=true',
'http://localhost:9999/spec/amd.html?headless=true'
],
detailedError: true, detailedError: true,
concurrency: 4, concurrency: 4,
browsers: [ browsers: [
@@ -180,40 +108,36 @@ module.exports = function(grunt) {
{browserName: 'firefox', platform: 'Linux'}, {browserName: 'firefox', platform: 'Linux'},
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'}, // {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
// {browserName: 'safari', version: 8, platform: 'OS X 10.10'}, // {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
{ {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
browserName: 'internet explorer', {browserName: 'internet explorer', version: 10, platform: 'Windows 8'}
version: 11,
platform: 'Windows 8.1'
},
{
browserName: 'internet explorer',
version: 10,
platform: 'Windows 8'
}
] ]
} }
}, },
sanity: { sanity: {
options: { options: {
build: process.env.TRAVIS_JOB_ID, build: process.env.TRAVIS_JOB_ID,
urls: [ 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'],
'http://localhost:9999/spec/umd.html?headless=true',
'http://localhost:9999/spec/amd-runtime.html?headless=true',
'http://localhost:9999/spec/umd-runtime.html?headless=true'
],
detailedError: true, detailedError: true,
concurrency: 2, concurrency: 2,
browsers: [{ browserName: 'chrome' }] browsers: [
{browserName: 'chrome'}
]
} }
} }
}, },
bgShell: { bgShell: {
checkTypes: {
cmd: 'npm run checkTypes',
bg: false,
fail: true
},
integrationTests: { integrationTests: {
cmd: './integration-testing/run-integration-tests.sh', cmd: './integration-testing/run-integration-tests.sh',
bg: false, bg: false,
fail: true fail: true
} }
}, },
watch: { watch: {
@@ -223,76 +147,46 @@ module.exports = function(grunt) {
}, },
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'], files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['on-file-change'] tasks: ['build', 'amd', 'tests', 'test']
} }
} }
}); });
// Build a new version of the library
this.registerTask('build', 'Builds a distributable version of the current project', [
'eslint',
'bgShell:checkTypes',
'parser',
'node',
'globals']);
this.registerTask('amd', ['babel:amd']);
this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);
this.registerTask('tests', ['concat:tests']);
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
// Load tasks from npm // Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-babel'); grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-bg-shell'); grunt.loadNpmTasks('grunt-bg-shell');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('@knappi/grunt-saucelabs'); grunt.loadNpmTasks('@knappi/grunt-saucelabs');
grunt.loadNpmTasks('grunt-webpack'); grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks'); grunt.task.loadTasks('tasks');
this.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'globals']
);
this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);
this.registerTask('release', 'Build final packages', [
'amd',
'uglify',
'test:min',
'copy:dist',
'copy:components',
'copy:cdnjs'
]);
this.registerTask('amd', ['babel:amd', 'requirejs']);
this.registerTask('test', ['test:bin', 'test:cov']);
grunt.registerTask('bench', ['metrics']); grunt.registerTask('bench', ['metrics']);
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
if (process.env.SAUCE_USERNAME) { grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'bgShell:integrationTests', 'sauce', 'metrics', 'publish:latest'] : ['default']);
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
} else {
grunt.registerTask('sauce', []);
}
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'bgShell:integrationTests',
'sauce',
'metrics',
'publish-to-aws'
]);
grunt.registerTask('on-file-change', [
'build',
'amd',
'concat:tests',
'test'
]);
// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']); grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']); grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('integration-tests', [ grunt.registerTask('integration-tests', ['default', 'bgShell:integrationTests']);
'default',
'bgShell:integrationTests'
]);
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011-2019 by Yehuda Katz Copyright (C) 2011-2017 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+9 -9
View File
@@ -11,12 +11,12 @@ Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be. keep the view and the code separated like we all know they should be.
Checkout the official Handlebars docs site at Checkout the official Handlebars docs site at
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/). [http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
Installing Installing
---------- ----------
See our [installation documentation](https://handlebarsjs.com/installation/). See our [installation documentation](http://handlebarsjs.com/installation.html).
Usage Usage
----- -----
@@ -46,23 +46,23 @@ var result = template(data);
// </ul> // </ul>
``` ```
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/). Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
Precompiling Templates Precompiling Templates
---------------------- ----------------------
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html). Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](http://handlebarsjs.com/precompilation.html).
Differences Between Handlebars.js and Mustache Differences Between Handlebars.js and Mustache
---------------------------------------------- ----------------------------------------------
Handlebars.js adds a couple of additional features to make writing Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work. templates easier and also changes a tiny detail of how partials work.
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions) - [Nested Paths](http://handlebarsjs.com/#paths)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers) - [Helpers](http://handlebarsjs.com/#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks) - [Block Expressions](http://handlebarsjs.com/#block-expressions)
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments) - [Literal Values](http://handlebarsjs.com/#literals)
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments) - [Delimited Comments](http://handlebarsjs.com/#comments)
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority. Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
+3 -2
View File
@@ -13,7 +13,8 @@ install:
# Clone submodules (mustache spec) # Clone submodules (mustache spec)
- cmd: git submodule update --init --recursive - cmd: git submodule update --init --recursive
# Install modules # Install modules
- cmd: npm ci - cmd: npm install
- cmd: npm install -g grunt-cli
# Post-install test scripts # Post-install test scripts
@@ -22,7 +23,7 @@ test_script:
- cmd: node --version - cmd: node --version
- cmd: npm --version - cmd: npm --version
# Run tests # Run tests
- cmd: npm run test - cmd: grunt --stack travis
# Don't actually build # Don't actually build
build: off build: off
+7
View File
@@ -0,0 +1,7 @@
module.exports = function(api) {
api.cache(false);
return {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
};
};
-1
View File
@@ -1,5 +1,4 @@
{ {
"extends": "prettier",
"globals": { "globals": {
"require": true "require": true
}, },
+3 -8
View File
@@ -6,9 +6,7 @@ module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'), var distFiles = fs.readdirSync('dist'),
distSizes = {}; distSizes = {};
async.each( async.each(distFiles, function(file, callback) {
distFiles,
function(file, callback) {
var content; var content;
try { try {
content = fs.readFileSync('dist/' + file); content = fs.readFileSync('dist/' + file);
@@ -34,10 +32,7 @@ module.exports = function(grunt, callback) {
}); });
}, },
function() { function() {
grunt.log.writeln( grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
);
callback([distSizes]); callback([distSizes]);
} });
);
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var metrics = fs.readdirSync(__dirname); var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) { metrics.forEach(function(metric) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) { if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
return; return;
} }
+2 -7
View File
@@ -9,16 +9,11 @@ module.exports = function(grunt, callback) {
_.each(templates, function(info, template) { _.each(templates, function(info, template) {
var src = info.handlebars, var src = info.handlebars,
compiled = Handlebars.precompile(src, {}), compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, { knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
knownHelpersOnly: true,
knownHelpers: info.helpers
});
templateSizes[template] = compiled.length; templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length; templateSizes['knownOnly_' + template] = knownHelpers.length;
}); });
grunt.log.writeln( grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
);
callback([templateSizes]); callback([templateSizes]);
}; };
+1 -2
View File
@@ -8,6 +8,5 @@ module.exports = {
bar: true bar: true
}, },
handlebars: handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
}; };
+1 -8
View File
@@ -1,12 +1,5 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{name}}{{/each}}', handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}', dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}', mustache: '{{#names}}{{name}}{{/names}}',
+1 -8
View File
@@ -1,11 +1,4 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#names}}{{name}}{{/names}}' handlebars: '{{#names}}{{name}}{{/names}}'
}; };
+1 -8
View File
@@ -1,11 +1,4 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}' handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
}; };
+1 -9
View File
@@ -1,13 +1,5 @@
module.exports = { module.exports = {
context: { context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
],
foo: 'bar'
},
handlebars: '{{#each names}}{{../foo}}{{/each}}', handlebars: '{{#each names}}{{../foo}}{{/each}}',
mustache: '{{#names}}{{foo}}{{/names}}', mustache: '{{#names}}{{foo}}{{/names}}',
eco: '<% for item in @names: %><%= @foo %><% end %>' eco: '<% for item in @names: %><%= @foo %><% end %>'
+3 -13
View File
@@ -1,16 +1,6 @@
module.exports = { module.exports = {
context: { context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' },
names: [ handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
{ bat: 'foo', name: ['Moe'] },
{ bat: 'foo', name: ['Larry'] },
{ bat: 'foo', name: ['Curly'] },
{ bat: 'foo', name: ['Shemp'] }
],
foo: 'bar'
},
handlebars:
'{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}', mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
eco: eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
'<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ var fs = require('fs');
var templates = fs.readdirSync(__dirname); var templates = fs.readdirSync(__dirname);
templates.forEach(function(template) { templates.forEach(function(template) {
if (template === 'index.js' || !/(.*)\.js$/.test(template)) { if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
return; return;
} }
module.exports[RegExp.$1] = require('./' + RegExp.$1); module.exports[RegExp.$1] = require('./' + RegExp.$1);
+1 -4
View File
@@ -1,8 +1,5 @@
module.exports = { module.exports = {
context: { context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
name: '1',
kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }]
},
partials: { partials: {
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' }, mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' } handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
+2 -10
View File
@@ -1,16 +1,8 @@
module.exports = { module.exports = {
context: { context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
peeps: [
{ name: 'Moe', count: 15 },
{ name: 'Larry', count: 5 },
{ name: 'Curly', count: 1 }
]
},
partials: { partials: {
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' }, mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
handlebars: { handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
variables: 'Hello {{name}}! You have {{count}} new messages.'
}
}, },
handlebars: '{{#each peeps}}{{>variables}}{{/each}}', handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
+2 -4
View File
@@ -1,9 +1,7 @@
module.exports = { module.exports = {
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } }, context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
handlebars: handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
'{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}', dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
eco: eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
'<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}' mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
}; };
+1
View File
@@ -5,3 +5,4 @@ module.exports = {
mustache: 'Hello {{name}}! You have {{count}} new messages.', mustache: 'Hello {{name}}! You have {{count}} new messages.',
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.' eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
}; };
+14 -38
View File
@@ -1,27 +1,19 @@
var _ = require('underscore'), var _ = require('underscore'),
runner = require('./util/template-runner'), runner = require('./util/template-runner'),
eco,
dust, eco, dust, Handlebars, Mustache;
Handlebars,
Mustache;
try { try {
dust = require('dustjs-linkedin'); dust = require('dustjs-linkedin');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
try { try {
Mustache = require('mustache'); Mustache = require('mustache');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
try { try {
eco = require('eco'); eco = require('eco');
} catch (err) { } catch (err) { /* NOP */ }
/* NOP */
}
function error() { function error() {
throw new Error('EWOT'); throw new Error('EWOT');
@@ -30,8 +22,10 @@ function error() {
function makeSuite(bench, name, template, handlebarsOnly) { function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree. // Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name, var templateName = name,
context = template.context, context = template.context,
partials = template.partials, partials = template.partials,
handlebarsOut, handlebarsOut,
compatOut, compatOut,
dustOut, dustOut,
@@ -39,19 +33,10 @@ function makeSuite(bench, name, template, handlebarsOnly) {
mustacheOut; mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}), var handlebar = Handlebars.compile(template.handlebars, {data: false}),
compat = Handlebars.compile(template.handlebars, { compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
data: false,
compat: true
}),
options = {helpers: template.helpers}; options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function( _.each(template.partials && template.partials.handlebars, function(partial, partialName) {
partial, Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
partialName
) {
Handlebars.registerPartial(
partialName,
Handlebars.compile(partial, { data: false })
);
}); });
handlebarsOut = handlebar(context, options); handlebarsOut = handlebar(context, options);
@@ -73,9 +58,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
dustOut = false; dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName)); dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dust.render(templateName, context, function(err, out) { dustOut = out; });
dustOut = out;
});
bench('dust', function() { bench('dust', function() {
dust.render(templateName, context, function() {}); dust.render(templateName, context, function() {});
@@ -124,16 +107,9 @@ function makeSuite(bench, name, template, handlebarsOnly) {
b = b.replace(/\s/g, ''); b = b.replace(/\s/g, '');
if (handlebarsOut !== b) { if (handlebarsOut !== b) {
throw new Error( throw new Error('Template output mismatch: ' + name
'Template output mismatch: ' + + '\n\nHandlebars: ' + handlebarsOut
name + + '\n\n' + lang + ': ' + b);
'\n\nHandlebars: ' +
handlebarsOut +
'\n\n' +
lang +
': ' +
b
);
} }
} }
+17 -44
View File
@@ -33,21 +33,16 @@ BenchWarmer.prototype = {
this.names.push(name); this.names.push(name);
} }
var first = this.first, var first = this.first, suiteName = this.suiteName, self = this;
suiteName = this.suiteName,
self = this;
this.first = false; this.first = false;
var bench = new Benchmark(fn, { var bench = new Benchmark(fn, {
name: this.suiteName + ': ' + name, name: this.suiteName + ': ' + name,
onComplete: function() { onComplete: function() {
if (first) { if (first) { self.startLine(suiteName); }
self.startLine(suiteName);
}
self.writeBench(bench); self.writeBench(bench);
self.currentBenches.push(bench); self.currentBenches.push(bench);
}, }, onError: function() {
onError: function() {
self.errors[this.name] = this; self.errors[this.name] = this;
} }
}); });
@@ -80,14 +75,10 @@ BenchWarmer.prototype = {
}); });
print('\n'); print('\n');
var errors = false, var errors = false, prop, bench;
prop,
bench;
for (prop in self.errors) { for (prop in self.errors) {
if ( if (Object.prototype.hasOwnProperty.call(self, prop)
Object.prototype.hasOwnProperty.call(self, prop) && && self.errors[prop].error.message !== 'EWOT') {
self.errors[prop].error.message !== 'EWOT'
) {
errors = true; errors = true;
break; break;
} }
@@ -116,39 +107,23 @@ BenchWarmer.prototype = {
}, },
scaleTimes: function() { scaleTimes: function() {
var scaled = (this.scaled = {}); var scaled = this.scaled = {};
_.each( _.each(this.times, function(times, name) {
this.times, var output = scaled[name] = {};
function(times, name) {
var output = (scaled[name] = {});
_.each( _.each(times, function(time, lang) {
times, output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
function(time, lang) { }, this);
output[lang] = ( }, this);
((time - this.minimum) / (this.maximum - this.minimum)) *
100
).toFixed(2);
},
this
);
},
this
);
}, },
printHeader: function(title, winners) { printHeader: function(title, winners) {
var benchSize = 0, var benchSize = 0, names = this.names, i, l;
names = this.names,
i,
l;
for (i = 0, l = names.length; i < l; i++) { for (i = 0, l = names.length; i < l; i++) {
var name = names[i]; var name = names[i];
if (benchSize < name.length) { if (benchSize < name.length) { benchSize = name.length; }
benchSize = name.length;
}
} }
this.nameSize = benchSize + 2; this.nameSize = benchSize + 2;
@@ -171,9 +146,7 @@ BenchWarmer.prototype = {
}, },
startLine: function(name) { startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function( var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
bench
) {
return bench.name.split(': ')[1]; return bench.name.split(': ')[1];
}); });
@@ -191,7 +164,7 @@ BenchWarmer.prototype = {
if (!bench.error) { if (!bench.error) {
var count = bench.hz, var count = bench.hz,
moe = (count * bench.stats.rme) / 100, moe = count * bench.stats.rme / 100,
minimum, minimum,
maximum; maximum;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "handlebars", "name": "handlebars",
"version": "4.7.0", "version": "4.5.3",
"main": "handlebars.js", "main": "handlebars.js",
"license": "MIT", "license": "MIT",
"dependencies": {} "dependencies": {}
+1 -1
View File
@@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>handlebars.js</id> <id>handlebars.js</id>
<version>4.7.0</version> <version>4.5.3</version>
<authors>handlebars.js Authors</authors> <authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl> <projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "handlebars", "name": "handlebars",
"version": "4.7.0", "version": "4.5.3",
"license": "MIT", "license": "MIT",
"jspm": { "jspm": {
"main": "handlebars", "main": "handlebars",
@@ -1,13 +1,12 @@
module.exports = { module.exports = {
extends: ['eslint:recommended', 'plugin:es5/no-es2015', 'prettier'], "extends": "eslint:recommended",
globals: { "globals": {
self: false "self": false
}, },
env: { "env": {
node: true "node": true
}, },
rules: { "rules": {
'no-console': 'off', 'no-console': 'off'
'no-var': 'off' }
} }
};
@@ -12,8 +12,7 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
# It does (almost) not test for correctness, because that is already done in the mocha-tests. # It does (almost) not test for correctness, because that is already done in the mocha-tests.
# And it does not use any NodeJS based testing framwork to make this part independent of the Node version. # And it does not use any NodeJS based testing framwork to make this part independent of the Node version.
unset npm_config_prefix # A list of NodeJS versions is expected as cli-args
echo "Handlebars should be able to run in various versions of NodeJS" echo "Handlebars should be able to run in various versions of NodeJS"
for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do
rm target node_modules package-lock.json -rf rm target node_modules package-lock.json -rf
@@ -10,6 +10,7 @@ testFiles
module.exports = { module.exports = {
entry: entryPoints, entry: entryPoints,
mode: 'production',
output: { output: {
filename: '[name]', filename: '[name]',
path: __dirname + '/dist' path: __dirname + '/dist'
@@ -21,7 +22,7 @@ module.exports = {
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { cacheDirectory: false } options: { cacheDirectory: false },
} }
} }
] ]
@@ -1,5 +1,6 @@
import Handlebars from 'handlebars'; import Handlebars from 'handlebars';
import {assertEquals} from './lib/assert'; import {assertEquals} from './lib/assert';
const template = Handlebars.compile('Author: {{author}}'); const template = Handlebars.compile('Author: {{author}}');
assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda'); assertEquals(template({author: 'Yehuda'}), 'Author: Yehuda');
@@ -3,8 +3,6 @@ import { assertEquals } from './lib/assert';
import testTemplate from './test-template.handlebars'; import testTemplate from './test-template.handlebars';
assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda'); assertEquals(testTemplate({author: 'Yehuda'}).trim(), 'Author: Yehuda');
const testTemplateRequire = require('./test-template.handlebars'); const testTemplateRequire = require('./test-template.handlebars');
assertEquals( assertEquals(testTemplateRequire({author: 'Yehuda'}).trim(), 'Author: Yehuda');
testTemplateRequire({ author: 'Yehuda' }).trim(),
'Author: Yehuda'
);
@@ -15,6 +15,8 @@ module.exports = {
path: __dirname + '/dist' path: __dirname + '/dist'
}, },
module: { module: {
rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }] rules: [
{test: /\.handlebars$/, loader: 'handlebars-loader'}
]
} }
}; };
+3 -10
View File
@@ -2,21 +2,16 @@ import runtime from './handlebars.runtime';
// Compiler imports // Compiler imports
import AST from './handlebars/compiler/ast'; import AST from './handlebars/compiler/ast';
import { import { parser as Parser, parse, parseWithoutProcessing } from './handlebars/compiler/base';
parser as Parser,
parse,
parseWithoutProcessing
} from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler'; import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler'; import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor'; import Visitor from './handlebars/compiler/visitor';
import noConflict from './handlebars/no-conflict'; import noConflict from './handlebars/no-conflict';
let _create = runtime.create;
function create() { function create() {
let hb = _create(); let hb = runtime.create();
hb.create = create;
hb.compile = function(input, options) { hb.compile = function(input, options) {
return compile(input, options, hb); return compile(input, options, hb);
}; };
@@ -35,8 +30,6 @@ function create() {
} }
let inst = create(); let inst = create();
inst.create = create;
noConflict(inst); noConflict(inst);
inst.Visitor = Visitor; inst.Visitor = Visitor;
+31 -10
View File
@@ -4,31 +4,52 @@ import * as base from './handlebars/base';
// (This is done to easily share code between commonjs and browse envs) // (This is done to easily share code between commonjs and browse envs)
import SafeString from './handlebars/safe-string'; import SafeString from './handlebars/safe-string';
import Exception from './handlebars/exception'; import Exception from './handlebars/exception';
import * as Utils from './handlebars/utils'; import {
import * as runtime from './handlebars/runtime'; extend,
toString,
isFunction,
isArray,
indexOf,
escapeExpression,
isEmpty,
createFrame,
blockParams,
appendContextPath
} from './handlebars/utils';
import {checkRevision, template, wrapProgram, resolvePartial, invokePartial, noop} from './handlebars/runtime';
import noConflict from './handlebars/no-conflict'; import noConflict from './handlebars/no-conflict';
// For compatibility and usage outside of module systems, make the Handlebars object a namespace // For compatibility and usage outside of module systems, make the Handlebars object a namespace
function create() { export function create() {
let hb = new base.HandlebarsEnvironment(); let hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base); extend(hb, base);
hb.SafeString = SafeString; hb.SafeString = SafeString;
hb.Exception = Exception; hb.Exception = Exception;
hb.Utils = Utils; hb.Utils = {
hb.escapeExpression = Utils.escapeExpression; extend,
toString,
hb.VM = runtime; isFunction,
isArray,
indexOf,
escapeExpression,
isEmpty,
createFrame,
blockParams,
appendContextPath
};
hb.escapeExpression = escapeExpression;
hb.create = create;
hb.VM = {checkRevision, template, wrapProgram, resolvePartial, invokePartial, noop};
hb.template = function(spec) { hb.template = function(spec) {
return runtime.template(spec, hb); return template(spec, hb);
}; };
return hb; return hb;
} }
let inst = create(); let inst = create();
inst.create = create;
noConflict(inst); noConflict(inst);
+4 -10
View File
@@ -4,7 +4,7 @@ import { registerDefaultHelpers } from './helpers';
import {registerDefaultDecorators} from './decorators'; import {registerDefaultDecorators} from './decorators';
import logger from './logger'; import logger from './logger';
export const VERSION = '4.7.0'; export const VERSION = '4.5.3';
export const COMPILER_REVISION = 8; export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7; export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
@@ -38,9 +38,7 @@ HandlebarsEnvironment.prototype = {
registerHelper: function(name, fn) { registerHelper: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name); extend(this.helpers, name);
} else { } else {
this.helpers[name] = fn; this.helpers[name] = fn;
@@ -55,9 +53,7 @@ HandlebarsEnvironment.prototype = {
extend(this.partials, name); extend(this.partials, name);
} else { } else {
if (typeof partial === 'undefined') { if (typeof partial === 'undefined') {
throw new Exception( throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
`Attempting to register a partial called "${name}" as undefined`
);
} }
this.partials[name] = partial; this.partials[name] = partial;
} }
@@ -68,9 +64,7 @@ HandlebarsEnvironment.prototype = {
registerDecorator: function(name, fn) { registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) { if (toString.call(name) === objectType) {
if (fn) { if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name); extend(this.decorators, name);
} else { } else {
this.decorators[name] = fn; this.decorators[name] = fn;
+6 -10
View File
@@ -5,28 +5,24 @@ let AST = {
// * it is an eligible helper, and // * it is an eligible helper, and
// * it has at least one parameter or hash segment // * it has at least one parameter or hash segment
helperExpression: function(node) { helperExpression: function(node) {
return ( return (node.type === 'SubExpression')
node.type === 'SubExpression' || || ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
((node.type === 'MustacheStatement' || && !!((node.params && node.params.length) || node.hash));
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
}, },
scopedId: function(path) { scopedId: function(path) {
return /^\.|this\b/.test(path.original); return (/^\.|this\b/).test(path.original);
}, },
// an ID is simple if it only has one part, and that part is not // an ID is simple if it only has one part, and that part is not
// `..` or `this`. // `..` or `this`.
simpleId: function(path) { simpleId: function(path) {
return ( return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
);
} }
} }
}; };
// Must be exported as an object rather than the root of the module as the jison lexer // Must be exported as an object rather than the root of the module as the jison lexer
// must modify the object to operate properly. // must modify the object to operate properly.
export default AST; export default AST;
+1 -3
View File
@@ -10,9 +10,7 @@ extend(yy, Helpers);
export function parseWithoutProcessing(input, options) { export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in. // Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { if (input.type === 'Program') { return input; }
return input;
}
parser.yy = yy; parser.yy = yy;
+8 -18
View File
@@ -1,16 +1,11 @@
/* global define */
import {isArray} from '../utils'; import {isArray} from '../utils';
let SourceNode; let SourceNode;
try { try {
/* istanbul ignore next */ // the 'source-map' map package is ignored in 'umd' and 'amd' builds (via webpack-config)
if (typeof define !== 'function' || !define.amd) {
// We don't support this in AMD environments. For these environments, we asusme that
// they are running on the browser and thus have no need for the source-map library.
let SourceMap = require('source-map'); let SourceMap = require('source-map');
SourceNode = SourceMap.SourceNode; SourceNode = SourceMap.SourceNode;
}
} catch (err) { } catch (err) {
/* NOP */ /* NOP */
} }
@@ -46,6 +41,7 @@ if (!SourceNode) {
}; };
} }
function castChunk(chunk, codeGen, loc) { function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) { if (isArray(chunk)) {
let ret = []; let ret = [];
@@ -61,6 +57,7 @@ function castChunk(chunk, codeGen, loc) {
return chunk; return chunk;
} }
function CodeGen(srcFile) { function CodeGen(srcFile) {
this.srcFile = srcFile; this.srcFile = srcFile;
this.source = []; this.source = [];
@@ -102,12 +99,7 @@ CodeGen.prototype = {
chunk = castChunk(chunk, this, loc); chunk = castChunk(chunk, this, loc);
return new SourceNode( return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
}, },
functionCall: function(fn, type, params) { functionCall: function(fn, type, params) {
@@ -116,17 +108,13 @@ CodeGen.prototype = {
}, },
quotedString: function(str) { quotedString: function(str) {
return ( return '"' + (str + '')
'"' +
(str + '')
.replace(/\\/g, '\\\\') .replace(/\\/g, '\\\\')
.replace(/"/g, '\\"') .replace(/"/g, '\\"')
.replace(/\n/g, '\\n') .replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') .replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + .replace(/\u2029/g, '\\u2029') + '"';
'"'
);
}, },
objectLiteral: function(obj) { objectLiteral: function(obj) {
@@ -145,6 +133,7 @@ CodeGen.prototype = {
return ret; return ret;
}, },
generateList: function(entries) { generateList: function(entries) {
let ret = this.empty(); let ret = this.empty();
@@ -169,3 +158,4 @@ CodeGen.prototype = {
}; };
export default CodeGen; export default CodeGen;
+26 -72
View File
@@ -25,10 +25,7 @@ Compiler.prototype = {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i], let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i]; otherOpcode = other.opcodes[i];
if ( if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false; return false;
} }
} }
@@ -57,20 +54,16 @@ Compiler.prototype = {
options.blockParams = options.blockParams || []; options.blockParams = options.blockParams || [];
options.knownHelpers = extend( options.knownHelpers = extend(Object.create(null), {
Object.create(null), 'helperMissing': true,
{ 'blockHelperMissing': true,
helperMissing: true, 'each': true,
blockHelperMissing: true, 'if': true,
each: true, 'unless': true,
if: true, 'with': true,
unless: true, 'log': true,
with: true, 'lookup': true
log: true, }, options.knownHelpers);
lookup: true
},
options.knownHelpers
);
return this.accept(program); return this.accept(program);
}, },
@@ -172,10 +165,7 @@ Compiler.prototype = {
let params = partial.params; let params = partial.params;
if (params.length > 1) { if (params.length > 1) {
throw new Exception( throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) { } else if (!params.length) {
if (this.options.explicitPartialContext) { if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined'); this.opcode('pushLiteral', 'undefined');
@@ -218,6 +208,7 @@ Compiler.prototype = {
this.DecoratorBlock(decorator); this.DecoratorBlock(decorator);
}, },
ContentStatement: function(content) { ContentStatement: function(content) {
if (content.value) { if (content.value) {
this.opcode('appendContent', content.value); this.opcode('appendContent', content.value);
@@ -269,21 +260,13 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) { if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name); this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) { } else if (this.options.knownHelpersOnly) {
throw new Exception( throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else { } else {
path.strict = true; path.strict = true;
path.falsy = true; path.falsy = true;
this.accept(path); this.accept(path);
this.opcode( this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
} }
}, },
@@ -304,13 +287,7 @@ Compiler.prototype = {
this.options.data = true; this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict); this.opcode('lookupData', path.depth, path.parts, path.strict);
} else { } else {
this.opcode( this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
} }
}, },
@@ -352,11 +329,7 @@ Compiler.prototype = {
// HELPERS // HELPERS
opcode: function(name) { opcode: function(name) {
this.opcodes.push({ this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc
});
}, },
addDepth: function(depth) { addDepth: function(depth) {
@@ -413,7 +386,9 @@ Compiler.prototype = {
if (this.stringParams) { if (this.stringParams) {
if (value.replace) { if (value.replace) {
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); value = value
.replace(/^(\.?\.\/)*/g, '')
.replace(/\//g, '.');
} }
if (val.depth) { if (val.depth) {
@@ -469,11 +444,7 @@ Compiler.prototype = {
}, },
blockParamIndex: function(name) { blockParamIndex: function(name) {
for ( for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth], let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name); param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) { if (blockParams && param >= 0) {
@@ -484,14 +455,8 @@ Compiler.prototype = {
}; };
export function precompile(input, options, env) { export function precompile(input, options, env) {
if ( if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
input == null || throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
input
);
} }
options = options || {}; options = options || {};
@@ -508,14 +473,8 @@ export function precompile(input, options, env) {
} }
export function compile(input, options = {}, env) { export function compile(input, options = {}, env) {
if ( if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
input == null || throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
input
);
} }
options = extend({}, options); options = extend({}, options);
@@ -531,12 +490,7 @@ export function compile(input, options = {}, env) {
function compileInput() { function compileInput() {
let ast = env.parse(input, options), let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options), environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile( templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
environment,
options,
undefined,
true
);
return env.template(templateSpec); return env.template(templateSpec);
} }
+11 -20
View File
@@ -6,10 +6,7 @@ function validateClose(open, close) {
if (open.path.original !== close) { if (open.path.original !== close) {
let errorNode = {loc: open.path.loc}; let errorNode = {loc: open.path.loc};
throw new Exception( throw new Exception(open.path.original + " doesn't match " + close, errorNode);
open.path.original + " doesn't match " + close,
errorNode
);
} }
} }
@@ -41,7 +38,8 @@ export function stripFlags(open, close) {
} }
export function stripComment(comment) { export function stripComment(comment) {
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, ''); return comment.replace(/^\{\{~?!-?-?/, '')
.replace(/-?-?~?\}\}$/, '');
} }
export function preparePath(data, parts, loc) { export function preparePath(data, parts, loc) {
@@ -84,7 +82,7 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
let escapeFlag = open.charAt(3) || open.charAt(2), let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&'; escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = /\*/.test(open); let decorator = (/\*/.test(open));
return { return {
type: decorator ? 'Decorator' : 'MustacheStatement', type: decorator ? 'Decorator' : 'MustacheStatement',
path, path,
@@ -120,30 +118,21 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
}; };
} }
export function prepareBlock( export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
openBlock,
program,
inverseAndProgram,
close,
inverted,
locInfo
) {
if (close && close.path) { if (close && close.path) {
validateClose(openBlock, close); validateClose(openBlock, close);
} }
let decorator = /\*/.test(openBlock.open); let decorator = (/\*/.test(openBlock.open));
program.blockParams = openBlock.blockParams; program.blockParams = openBlock.blockParams;
let inverse, inverseStrip; let inverse,
inverseStrip;
if (inverseAndProgram) { if (inverseAndProgram) {
if (decorator) { if (decorator) {
throw new Exception( throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
'Unexpected inverse block on decorator',
inverseAndProgram
);
} }
if (inverseAndProgram.chain) { if (inverseAndProgram.chain) {
@@ -203,6 +192,7 @@ export function prepareProgram(statements, loc) {
}; };
} }
export function preparePartialBlock(open, program, close, locInfo) { export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close); validateClose(open, close);
@@ -217,3 +207,4 @@ export function preparePartialBlock(open, program, close, locInfo) {
loc: this.locInfo(locInfo) loc: this.locInfo(locInfo)
}; };
} }
+61 -188
View File
@@ -2,6 +2,7 @@ import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
import Exception from '../exception'; import Exception from '../exception';
import {isArray} from '../utils'; import {isArray} from '../utils';
import CodeGen from './code-gen'; import CodeGen from './code-gen';
import {dangerousPropertyRegex} from '../helpers/lookup';
function Literal(value) { function Literal(value) {
this.value = value; this.value = value;
@@ -13,7 +14,19 @@ JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide // PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics // alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name/* , type*/) { nameLookup: function(parent, name/* , type*/) {
return this.internalNameLookup(parent, name); if (dangerousPropertyRegex.test(name)) {
const isEnumerable = [ this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',', JSON.stringify(name), ')'];
return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
}
return _actualLookup();
function _actualLookup() {
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
return [parent, '.', name];
} else {
return [parent, '[', JSON.stringify(name), ']'];
}
}
}, },
depthedLookup: function(name) { depthedLookup: function(name) {
return [this.aliasable('container.lookup'), '(depths, "', name, '")']; return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
@@ -49,12 +62,6 @@ JavaScriptCompiler.prototype = {
return this.quotedString(''); return this.quotedString('');
}, },
// END PUBLIC API // END PUBLIC API
internalNameLookup: function(parent, name) {
this.lookupPropertyFunctionIsUsed = true;
return ['lookupProperty(', parent, ',', JSON.stringify(name), ')'];
},
lookupPropertyFunctionIsUsed: false,
compile: function(environment, options, context, asObject) { compile: function(environment, options, context, asObject) {
this.environment = environment; this.environment = environment;
@@ -84,11 +91,7 @@ JavaScriptCompiler.prototype = {
this.compileChildren(environment, options); this.compileChildren(environment, options);
this.useDepths = this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
this.useDepths ||
environment.useDepths ||
environment.useDecorators ||
this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams; this.useBlockParams = this.useBlockParams || environment.useBlockParams;
let opcodes = environment.opcodes, let opcodes = environment.opcodes,
@@ -117,28 +120,13 @@ JavaScriptCompiler.prototype = {
if (!this.decorators.isEmpty()) { if (!this.decorators.isEmpty()) {
this.useDecorators = true; this.useDecorators = true;
this.decorators.prepend([ this.decorators.prepend('var decorators = container.decorators;\n');
'var decorators = container.decorators, ',
this.lookupPropertyFunctionVarDeclaration(),
';\n'
]);
this.decorators.push('return fn;'); this.decorators.push('return fn;');
if (asObject) { if (asObject) {
this.decorators = Function.apply(this, [ this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
'fn',
'props',
'container',
'depth0',
'data',
'blockParams',
'depths',
this.decorators.merge()
]);
} else { } else {
this.decorators.prepend( this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
'function(fn, props, container, depth0, data, blockParams, depths) {\n'
);
this.decorators.push('}\n'); this.decorators.push('}\n');
this.decorators = this.decorators.merge(); this.decorators = this.decorators.merge();
} }
@@ -233,15 +221,11 @@ JavaScriptCompiler.prototype = {
Object.keys(this.aliases).forEach(alias => { Object.keys(this.aliases).forEach(alias => {
let node = this.aliases[alias]; let node = this.aliases[alias];
if (node.children && node.referenceCount > 1) { if (node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + ++aliasCount + '=' + alias; varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
node.children[0] = 'alias' + aliasCount; node.children[0] = 'alias' + aliasCount;
} }
}); });
if (this.lookupPropertyFunctionIsUsed) {
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
}
let params = ['container', 'depth0', 'helpers', 'partials', 'data']; let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
if (this.useBlockParams || this.useDepths) { if (this.useBlockParams || this.useDepths) {
@@ -259,23 +243,18 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params); return Function.apply(this, params);
} else { } else {
return this.source.wrap([ return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
'function(',
params.join(','),
') {\n ',
source,
'}'
]);
} }
}, },
mergeSource: function(varDeclarations) { mergeSource: function(varDeclarations) {
let isSimple = this.environment.isSimple, let isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer, appendOnly = !this.forceBuffer,
appendFirst, appendFirst,
sourceSeen, sourceSeen,
bufferStart, bufferStart,
bufferEnd; bufferEnd;
this.source.each(line => { this.source.each((line) => {
if (line.appendToBuffer) { if (line.appendToBuffer) {
if (bufferStart) { if (bufferStart) {
line.prepend(' + '); line.prepend(' + ');
@@ -301,6 +280,7 @@ JavaScriptCompiler.prototype = {
} }
}); });
if (appendOnly) { if (appendOnly) {
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return '); bufferStart.prepend('return ');
@@ -309,8 +289,7 @@ JavaScriptCompiler.prototype = {
this.source.push('return "";'); this.source.push('return "";');
} }
} else { } else {
varDeclarations += varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
if (bufferStart) { if (bufferStart) {
bufferStart.prepend('return buffer + '); bufferStart.prepend('return buffer + ');
@@ -321,25 +300,12 @@ JavaScriptCompiler.prototype = {
} }
if (varDeclarations) { if (varDeclarations) {
this.source.prepend( this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')
);
} }
return this.source.merge(); return this.source.merge();
}, },
lookupPropertyFunctionVarDeclaration: function() {
return `
lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
}
`.trim();
},
// [blockValue] // [blockValue]
// //
// On stack, before: hash, inverse, program, value // On stack, before: hash, inverse, program, value
@@ -350,9 +316,7 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly // replace it on the stack with the result of properly
// invoking blockHelperMissing. // invoking blockHelperMissing.
blockValue: function(name) { blockValue: function(name) {
let blockHelperMissing = this.aliasable( let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)]; params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params); this.setupHelperArgs(name, 0, params);
@@ -370,9 +334,7 @@ JavaScriptCompiler.prototype = {
// On stack, after, if lastHelper: value // On stack, after, if lastHelper: value
ambiguousBlockValue: function() { ambiguousBlockValue: function() {
// We're being a bit cheeky and reusing the options value from the prior exec // We're being a bit cheeky and reusing the options value from the prior exec
let blockHelperMissing = this.aliasable( let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
'container.hooks.blockHelperMissing'
),
params = [this.contextName(0)]; params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true); this.setupHelperArgs('', 0, params, true);
@@ -382,14 +344,9 @@ JavaScriptCompiler.prototype = {
params.splice(1, 0, current); params.splice(1, 0, current);
this.pushSource([ this.pushSource([
'if (!', 'if (!', this.lastHelper, ') { ',
this.lastHelper, current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
') { ', '}']);
current,
' = ',
this.source.functionCall(blockHelperMissing, 'call', params),
'}'
]);
}, },
// [appendContent] // [appendContent]
@@ -419,24 +376,14 @@ JavaScriptCompiler.prototype = {
// Otherwise, the empty string is appended // Otherwise, the empty string is appended
append: function() { append: function() {
if (this.isInline()) { if (this.isInline()) {
this.replaceStack(current => [' != null ? ', current, ' : ""']); this.replaceStack((current) => [' != null ? ', current, ' : ""']);
this.pushSource(this.appendToBuffer(this.popStack())); this.pushSource(this.appendToBuffer(this.popStack()));
} else { } else {
let local = this.popStack(); let local = this.popStack();
this.pushSource([ this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
'if (',
local,
' != null) { ',
this.appendToBuffer(local, undefined, true),
' }'
]);
if (this.environment.isSimple) { if (this.environment.isSimple) {
this.pushSource([ this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
'else { ',
this.appendToBuffer("''", undefined, true),
' }'
]);
} }
} }
}, },
@@ -448,14 +395,8 @@ JavaScriptCompiler.prototype = {
// //
// Escape `value` and append it to the buffer // Escape `value` and append it to the buffer
appendEscaped: function() { appendEscaped: function() {
this.pushSource( this.pushSource(this.appendToBuffer(
this.appendToBuffer([ [this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
this.aliasable('container.escapeExpression'),
'(',
this.popStack(),
')'
])
);
}, },
// [getContext] // [getContext]
@@ -539,7 +480,7 @@ JavaScriptCompiler.prototype = {
let len = parts.length; let len = parts.length;
for (; i < len; i++) { for (; i < len; i++) {
/* eslint-disable no-loop-func */ /* eslint-disable no-loop-func */
this.replaceStack(current => { this.replaceStack((current) => {
let lookup = this.nameLookup(current, parts[i], type); let lookup = this.nameLookup(current, parts[i], type);
// We want to ensure that zero and false are handled properly if the context (falsy flag) // We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values. // needs to have the special handling for these values.
@@ -562,14 +503,7 @@ JavaScriptCompiler.prototype = {
// If the `value` is a lambda, replace it on the stack by // If the `value` is a lambda, replace it on the stack by
// the return value of the lambda // the return value of the lambda
resolvePossibleLambda: function() { resolvePossibleLambda: function() {
this.push([ this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
this.aliasable('container.lambda'),
'(',
this.popStack(),
', ',
this.contextName(0),
')'
]);
}, },
// [pushStringParam] // [pushStringParam]
@@ -677,12 +611,7 @@ JavaScriptCompiler.prototype = {
this.decorators.push([ this.decorators.push([
'fn = ', 'fn = ',
this.decorators.functionCall(foundDecorator, '', [ this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
'fn',
'props',
'container',
options
]),
' || fn;' ' || fn;'
]); ]);
}, },
@@ -702,28 +631,17 @@ JavaScriptCompiler.prototype = {
let possibleFunctionCalls = []; let possibleFunctionCalls = [];
if (isSimple) { if (isSimple) { // direct call to helper
// direct call to helper
possibleFunctionCalls.push(helper.name); possibleFunctionCalls.push(helper.name);
} }
// call a function from the input object // call a function from the input object
possibleFunctionCalls.push(nonHelper); possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) { if (!this.options.strict) {
possibleFunctionCalls.push( possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
this.aliasable('container.hooks.helperMissing')
);
} }
let functionLookupCode = [ let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
'(', let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
this.itemsSeparatedBy(possibleFunctionCalls, '||'),
')'
];
let functionCall = this.source.functionCall(
functionLookupCode,
'call',
helper.callParams
);
this.push(functionCall); this.push(functionCall);
}, },
@@ -767,11 +685,7 @@ JavaScriptCompiler.prototype = {
this.emptyHash(); this.emptyHash();
let helper = this.setupHelper(0, name, helperCall); let helper = this.setupHelper(0, name, helperCall);
let helperName = (this.lastHelper = this.nameLookup( let helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
'helpers',
name,
'helper'
));
let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; let lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) { if (!this.options.strict) {
@@ -783,15 +697,10 @@ JavaScriptCompiler.prototype = {
} }
this.push([ this.push([
'(', '(', lookup,
lookup, (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
helper.paramsInit ? ['),(', helper.paramsInit] : [], '(typeof helper === ', this.aliasable('"function"'), ' ? ',
'),', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))'
'(typeof helper === ',
this.aliasable('"function"'),
' ? ',
this.source.functionCall('helper', 'call', helper.callParams),
' : helper))'
]); ]);
}, },
@@ -869,13 +778,8 @@ JavaScriptCompiler.prototype = {
pushId: function(type, name, child) { pushId: function(type, name, child) {
if (type === 'BlockParam') { if (type === 'BlockParam') {
this.pushStackLiteral( this.pushStackLiteral(
'blockParams[' + 'blockParams[' + name[0] + '].path[' + name[1] + ']'
name[0] + + (child ? ' + ' + JSON.stringify('.' + child) : ''));
'].path[' +
name[1] +
']' +
(child ? ' + ' + JSON.stringify('.' + child) : '')
);
} else if (type === 'PathExpression') { } else if (type === 'PathExpression') {
this.pushString(name); this.pushString(name);
} else if (type === 'SubExpression') { } else if (type === 'SubExpression') {
@@ -890,9 +794,7 @@ JavaScriptCompiler.prototype = {
compiler: JavaScriptCompiler, compiler: JavaScriptCompiler,
compileChildren: function(environment, options) { compileChildren: function(environment, options) {
let children = environment.children, let children = environment.children, child, compiler;
child,
compiler;
for (let i = 0, l = children.length; i < l; i++) { for (let i = 0, l = children.length; i < l; i++) {
child = children[i]; child = children[i];
@@ -905,12 +807,7 @@ JavaScriptCompiler.prototype = {
let index = this.context.programs.length; let index = this.context.programs.length;
child.index = index; child.index = index;
child.name = 'program' + index; child.name = 'program' + index;
this.context.programs[index] = compiler.compile( this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
child,
options,
this.context,
!this.precompile
);
this.context.decorators[index] = compiler.decorators; this.context.decorators[index] = compiler.decorators;
this.context.environments[index] = child; this.context.environments[index] = child;
@@ -973,11 +870,7 @@ JavaScriptCompiler.prototype = {
pushSource: function(source) { pushSource: function(source) {
if (this.pendingContent) { if (this.pendingContent) {
this.source.push( this.source.push(
this.appendToBuffer( this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.source.quotedString(this.pendingContent),
this.pendingLocation
)
);
this.pendingContent = undefined; this.pendingContent = undefined;
} }
@@ -1027,9 +920,7 @@ JavaScriptCompiler.prototype = {
incrStack: function() { incrStack: function() {
this.stackSlot++; this.stackSlot++;
if (this.stackSlot > this.stackVars.length) { if (this.stackSlot > this.stackVars.length) { this.stackVars.push('stack' + this.stackSlot); }
this.stackVars.push('stack' + this.stackSlot);
}
return this.topStackName(); return this.topStackName();
}, },
topStackName: function() { topStackName: function() {
@@ -1058,7 +949,7 @@ JavaScriptCompiler.prototype = {
let inline = this.isInline(), let inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop(); item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && item instanceof Literal) { if (!wrapped && (item instanceof Literal)) {
return item.value; return item.value;
} else { } else {
if (!inline) { if (!inline) {
@@ -1073,7 +964,7 @@ JavaScriptCompiler.prototype = {
}, },
topStack: function() { topStack: function() {
let stack = this.isInline() ? this.inlineStack : this.compileStack, let stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1]; item = stack[stack.length - 1];
/* istanbul ignore if */ /* istanbul ignore if */
@@ -1118,11 +1009,7 @@ JavaScriptCompiler.prototype = {
let params = [], let params = [],
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
let foundHelper = this.nameLookup('helpers', name, 'helper'), let foundHelper = this.nameLookup('helpers', name, 'helper'),
callContext = this.aliasable( callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`);
`${this.contextName(0)} != null ? ${this.contextName(
0
)} : (container.nullContext || {})`
);
return { return {
params: params, params: params,
@@ -1219,6 +1106,7 @@ JavaScriptCompiler.prototype = {
} }
}; };
(function() { (function() {
const reservedWords = ( const reservedWords = (
'break else new var' + 'break else new var' +
@@ -1239,21 +1127,15 @@ JavaScriptCompiler.prototype = {
' null true false' ' null true false'
).split(' '); ).split(' ');
const compilerWords = (JavaScriptCompiler.RESERVED_WORDS = {}); const compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for (let i = 0, l = reservedWords.length; i < l; i++) { for (let i = 0, l = reservedWords.length; i < l; i++) {
compilerWords[reservedWords[i]] = true; compilerWords[reservedWords[i]] = true;
} }
})(); }());
/**
* @deprecated May be removed in the next major version
*/
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
return ( return !JavaScriptCompiler.RESERVED_WORDS[name] && (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/).test(name);
!JavaScriptCompiler.RESERVED_WORDS[name] &&
/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)
);
}; };
function strictLookup(requireTerminal, compiler, parts, type) { function strictLookup(requireTerminal, compiler, parts, type) {
@@ -1269,16 +1151,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
} }
if (requireTerminal) { if (requireTerminal) {
return [ return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )'];
compiler.aliasable('container.strict'),
'(',
stack,
', ',
compiler.quotedString(parts[i]),
', ',
JSON.stringify(compiler.source.currentLocation),
' )'
];
} else { } else {
return stack; return stack;
} }
+7 -14
View File
@@ -25,8 +25,7 @@ PrintVisitor.prototype.pad = function(string) {
PrintVisitor.prototype.Program = function(program) { PrintVisitor.prototype.Program = function(program) {
let out = '', let out = '',
body = program.body, body = program.body,
i, i, l;
l;
if (program.blockParams) { if (program.blockParams) {
let blockParams = 'BLOCK PARAMS: ['; let blockParams = 'BLOCK PARAMS: [';
@@ -53,14 +52,11 @@ PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}'); return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
}; };
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function( PrintVisitor.prototype.BlockStatement =
block PrintVisitor.prototype.DecoratorBlock = function(block) {
) {
let out = ''; let out = '';
out += this.pad( out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++; this.padding++;
out += this.pad(this.SubExpression(block)); out += this.pad(this.SubExpression(block));
if (block.program) { if (block.program) {
@@ -70,16 +66,12 @@ PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock =
this.padding--; this.padding--;
} }
if (block.inverse) { if (block.inverse) {
if (block.program) { if (block.program) { this.padding++; }
this.padding++;
}
out += this.pad('{{^}}'); out += this.pad('{{^}}');
this.padding++; this.padding++;
out += this.accept(block.inverse); out += this.accept(block.inverse);
this.padding--; this.padding--;
if (block.program) { if (block.program) { this.padding--; }
this.padding--;
}
} }
this.padding--; this.padding--;
@@ -142,6 +134,7 @@ PrintVisitor.prototype.PathExpression = function(id) {
return (id.data ? '@' : '') + 'PATH:' + path; return (id.data ? '@' : '') + 'PATH:' + path;
}; };
PrintVisitor.prototype.StringLiteral = function(string) { PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"'; return '"' + string.value + '"';
}; };
+1 -8
View File
@@ -15,14 +15,7 @@ Visitor.prototype = {
// Hacky sanity check: This may have a few false positives for type for the helper // Hacky sanity check: This may have a few false positives for type for the helper
// methods but will generally do the right thing without a lot of overhead. // methods but will generally do the right thing without a lot of overhead.
if (value && !Visitor.prototype[value.type]) { if (value && !Visitor.prototype[value.type]) {
throw new Exception( throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
'Unexpected node type "' +
value.type +
'" found when accepting ' +
name +
' on ' +
node.type
);
} }
node[name] = value; node[name] = value;
} }
+20 -38
View File
@@ -22,10 +22,10 @@ WhitespaceControl.prototype.Program = function(program) {
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
_isNextWhitespace = isNextWhitespace(body, i, isRoot), _isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace, openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace, closeStandalone = strip.closeStandalone && _isNextWhitespace,
inlineStandalone = inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
if (strip.close) { if (strip.close) {
omitRight(body, i, true); omitRight(body, i, true);
@@ -41,7 +41,7 @@ WhitespaceControl.prototype.Program = function(program) {
// If we are on a standalone node, save the indent info for partials // If we are on a standalone node, save the indent info for partials
if (current.type === 'PartialStatement') { if (current.type === 'PartialStatement') {
// Pull out the whitespace from the final line // Pull out the whitespace from the final line
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
} }
} }
} }
@@ -62,9 +62,9 @@ WhitespaceControl.prototype.Program = function(program) {
return program; return program;
}; };
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function( WhitespaceControl.prototype.BlockStatement =
block WhitespaceControl.prototype.DecoratorBlock =
) { WhitespaceControl.prototype.PartialBlockStatement = function(block) {
this.accept(block.program); this.accept(block.program);
this.accept(block.inverse); this.accept(block.inverse);
@@ -112,11 +112,9 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
} }
// Find standalone else statments // Find standalone else statments
if ( if (!this.options.ignoreStandalone
!this.options.ignoreStandalone && && isPrevWhitespace(program.body)
isPrevWhitespace(program.body) && && isNextWhitespace(firstInverse.body)) {
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body); omitLeft(program.body);
omitRight(firstInverse.body); omitRight(firstInverse.body);
} }
@@ -127,15 +125,13 @@ WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.Decorat
return strip; return strip;
}; };
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function( WhitespaceControl.prototype.Decorator =
mustache WhitespaceControl.prototype.MustacheStatement = function(mustache) {
) {
return mustache.strip; return mustache.strip;
}; };
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function( WhitespaceControl.prototype.PartialStatement =
node WhitespaceControl.prototype.CommentStatement = function(node) {
) {
/* istanbul ignore next */ /* istanbul ignore next */
let strip = node.strip || {}; let strip = node.strip || {};
return { return {
@@ -145,6 +141,7 @@ WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.Comme
}; };
}; };
function isPrevWhitespace(body, i, isRoot) { function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) { if (i === undefined) {
i = body.length; i = body.length;
@@ -159,9 +156,7 @@ function isPrevWhitespace(body, i, isRoot) {
} }
if (prev.type === 'ContentStatement') { if (prev.type === 'ContentStatement') {
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test( return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
prev.original
);
} }
} }
function isNextWhitespace(body, i, isRoot) { function isNextWhitespace(body, i, isRoot) {
@@ -176,9 +171,7 @@ function isNextWhitespace(body, i, isRoot) {
} }
if (next.type === 'ContentStatement') { if (next.type === 'ContentStatement') {
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test( return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
next.original
);
} }
} }
@@ -191,19 +184,12 @@ function isNextWhitespace(body, i, isRoot) {
// content is met. // content is met.
function omitRight(body, i, multiple) { function omitRight(body, i, multiple) {
let current = body[i == null ? 0 : i + 1]; let current = body[i == null ? 0 : i + 1];
if ( if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.rightStripped)
) {
return; return;
} }
let original = current.value; let original = current.value;
current.value = current.value.replace( current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
''
);
current.rightStripped = current.value !== original; current.rightStripped = current.value !== original;
} }
@@ -216,17 +202,13 @@ function omitRight(body, i, multiple) {
// content is met. // content is met.
function omitLeft(body, i, multiple) { function omitLeft(body, i, multiple) {
let current = body[i == null ? body.length - 1 : i - 1]; let current = body[i == null ? body.length - 1 : i - 1];
if ( if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.leftStripped)
) {
return; return;
} }
// We omit the last node if it's whitespace only and not preceded by a non-content node. // We omit the last node if it's whitespace only and not preceded by a non-content node.
let original = current.value; let original = current.value;
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
current.leftStripped = current.value !== original; current.leftStripped = current.value !== original;
return current.leftStripped; return current.leftStripped;
} }
+1
View File
@@ -3,3 +3,4 @@ import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) { export function registerDefaultDecorators(instance) {
registerInline(instance); registerInline(instance);
} }
+2 -10
View File
@@ -1,13 +1,5 @@
const errorProps = [
'description', const errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack'];
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];
function Exception(message, node) { function Exception(message, node) {
let loc = node && node.loc, let loc = node && node.loc,
@@ -22,10 +22,7 @@ export default function(instance) {
} else { } else {
if (options.data && options.ids) { if (options.data && options.ids) {
let data = createFrame(options.data); let data = createFrame(options.data);
data.contextPath = appendContextPath( data.contextPath = appendContextPath(options.data.contextPath, options.name);
options.data.contextPath,
options.name
);
options = {data: data}; options = {data: data};
} }
+5 -19
View File
@@ -1,10 +1,4 @@
import { import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
appendContextPath,
blockParams,
createFrame,
isArray,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
@@ -21,13 +15,10 @@ export default function(instance) {
contextPath; contextPath;
if (options.data && options.ids) { if (options.data && options.ids) {
contextPath = contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
} }
if (isFunction(context)) { if (isFunction(context)) { context = context.call(this); }
context = context.call(this);
}
if (options.data) { if (options.data) {
data = createFrame(options.data); data = createFrame(options.data);
@@ -45,14 +36,9 @@ export default function(instance) {
} }
} }
ret = ret = ret + fn(context[field], {
ret +
fn(context[field], {
data: data, data: data,
blockParams: blockParams( blockParams: blockParams([context[field], field], [contextPath + field, null])
[context[field], field],
[contextPath + field, null]
)
}); });
} }
+1 -3
View File
@@ -7,9 +7,7 @@ export default function(instance) {
return undefined; return undefined;
} else { } else {
// Someone is actually trying to call something, blow up. // Someone is actually trying to call something, blow up.
throw new Exception( throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
} }
}); });
} }
+4 -14
View File
@@ -3,12 +3,8 @@ import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('if', function(conditional, options) { instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#if requires exactly one argument');}
throw new Exception('#if requires exactly one argument'); if (isFunction(conditional)) { conditional = conditional.call(this); }
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// Default behavior is to render the positive path if the value is truthy and not empty. // Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the // The `includeZero` option may be set to treat the condtional as purely not empty based on the
@@ -21,13 +17,7 @@ export default function(instance) {
}); });
instance.registerHelper('unless', function(conditional, options) { instance.registerHelper('unless', function(conditional, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#unless requires exactly one argument');}
throw new Exception('#unless requires exactly one argument'); return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
}); });
} }
+7 -3
View File
@@ -1,9 +1,13 @@
export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
export default function(instance) { export default function(instance) {
instance.registerHelper('lookup', function(obj, field, options) { instance.registerHelper('lookup', function(obj, field) {
if (!obj) { if (!obj) {
// Note for 5.0: Change to "obj == null" in 5.0
return obj; return obj;
} }
return options.lookupProperty(obj, field); if (dangerousPropertyRegex.test(String(field)) && !Object.prototype.propertyIsEnumerable.call(obj, field)) {
return undefined;
}
return obj[field];
}); });
} }
+4 -17
View File
@@ -1,20 +1,10 @@
import { import { appendContextPath, blockParams, createFrame, isEmpty, isFunction } from '../utils';
appendContextPath,
blockParams,
createFrame,
isEmpty,
isFunction
} from '../utils';
import Exception from '../exception'; import Exception from '../exception';
export default function(instance) { export default function(instance) {
instance.registerHelper('with', function(context, options) { instance.registerHelper('with', function(context, options) {
if (arguments.length != 2) { if (arguments.length != 2) { throw new Exception('#with requires exactly one argument');}
throw new Exception('#with requires exactly one argument'); if (isFunction(context)) { context = context.call(this); }
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn; let fn = options.fn;
@@ -22,10 +12,7 @@ export default function(instance) {
let data = options.data; let data = options.data;
if (options.data && options.ids) { if (options.data && options.ids) {
data = createFrame(options.data); data = createFrame(options.data);
data.contextPath = appendContextPath( data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]);
options.data.contextPath,
options.ids[0]
);
} }
return fn(context, { return fn(context, {
@@ -1,11 +0,0 @@
import { extend } from '../utils';
/**
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
* The resulting object can be used with "object[property]" to check if a property exists
* @param {...object} sources a varargs parameter of source objects that will be merged
* @returns {object}
*/
export function createNewLookupObject(...sources) {
return extend(Object.create(null), ...sources);
}
-56
View File
@@ -1,56 +0,0 @@
import { createNewLookupObject } from './create-new-lookup-object';
import * as logger from '../logger';
export function createProtoAccessControl(runtimeOptions) {
let defaultMethodWhiteList = Object.create(null);
defaultMethodWhiteList['constructor'] = false;
defaultMethodWhiteList['__defineGetter__'] = false;
defaultMethodWhiteList['__defineSetter__'] = false;
defaultMethodWhiteList['__lookupGetter__'] = false;
let defaultPropertyWhiteList = Object.create(null);
// eslint-disable-next-line no-proto
defaultPropertyWhiteList['__proto__'] = false;
return {
properties: {
whitelist: createNewLookupObject(
defaultPropertyWhiteList,
runtimeOptions.allowedProtoProperties
),
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
},
methods: {
whitelist: createNewLookupObject(
defaultMethodWhiteList,
runtimeOptions.allowedProtoMethods
),
defaultValue: runtimeOptions.allowProtoMethodsByDefault
}
};
}
export function resultIsAllowed(result, protoAccessControl, propertyName) {
if (typeof result === 'function') {
return checkWhiteList(protoAccessControl.methods, propertyName);
} else {
return checkWhiteList(protoAccessControl.properties, propertyName);
}
}
function checkWhiteList(protoAccessControlForType, propertyName) {
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
return protoAccessControlForType.whitelist[propertyName] === true;
}
if (protoAccessControlForType.defaultValue !== undefined) {
return protoAccessControlForType.defaultValue;
}
// eslint-disable-next-line no-console
logger.log(
'error',
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
`You can add a runtime option to disable the check or this warning:\n` +
`See http://localhost:8080/api-reference/runtime-options.html#options-to-control-prototype-access for details`
);
return false;
}
-8
View File
@@ -1,8 +0,0 @@
export function wrapHelper(helper, transformOptionsFn) {
let wrapper = function(/* dynamic arguments */) {
const options = arguments[arguments.length - 1];
arguments[arguments.length - 1] = transformOptionsFn(options);
return helper.apply(this, arguments);
};
return wrapper;
}
+2 -6
View File
@@ -22,13 +22,9 @@ let logger = {
log: function(level, ...message) { log: function(level, ...message) {
level = logger.lookupLevel(level); level = logger.lookupLevel(level);
if ( if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level]; let method = logger.methodMap[level];
// eslint-disable-next-line no-console if (!console[method]) { // eslint-disable-line no-console
if (!console[method]) {
method = 'log'; method = 'log';
} }
console[method](...message); // eslint-disable-line no-console console[method](...message); // eslint-disable-line no-console
+1
View File
@@ -1,3 +1,4 @@
/* global window */
export default function(Handlebars) { export default function(Handlebars) {
/* istanbul ignore next */ /* istanbul ignore next */
let root = typeof global !== 'undefined' ? global : window, let root = typeof global !== 'undefined' ? global : window,
+41 -198
View File
@@ -1,52 +1,30 @@
import * as Utils from './utils'; import {extend, escapeExpression} from './utils';
import Exception from './exception'; import Exception from './exception';
import { import {COMPILER_REVISION, createFrame, LAST_COMPATIBLE_COMPILER_REVISION, REVISION_CHANGES} from './base';
COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES
} from './base';
import {moveHelperToHooks} from './helpers'; import {moveHelperToHooks} from './helpers';
import { wrapHelper } from './internal/wrapHelper';
import {
createProtoAccessControl,
resultIsAllowed
} from './internal/proto-access';
export function checkRevision(compilerInfo) { export function checkRevision(compilerInfo) {
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1, const compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION; currentRevision = COMPILER_REVISION;
if ( if (compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= COMPILER_REVISION) {
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return; return;
} }
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) { if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision], const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision]; compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception( throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
'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 + ').');
'Please update your precompiler to a newer version (' +
runtimeVersions +
') or downgrade your runtime to an older version (' +
compilerVersions +
').'
);
} else { } else {
// Use the embedded version info since the runtime doesn't know about this revision yet // Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception( throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
'Please update your runtime to a newer version (' +
compilerInfo[1] +
').'
);
} }
} }
export function template(templateSpec, env) { export function template(templateSpec, env) {
/* istanbul ignore next */ /* istanbul ignore next */
if (!env) { if (!env) {
throw new Exception('No environment passed to template'); throw new Exception('No environment passed to template');
@@ -62,37 +40,24 @@ export function template(templateSpec, env) {
env.VM.checkRevision(templateSpec.compiler); env.VM.checkRevision(templateSpec.compiler);
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
const templateWasPrecompiledWithCompilerV7 = const templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7;
templateSpec.compiler && templateSpec.compiler[0] === 7;
function invokePartialWrapper(partial, context, options) { function invokePartialWrapper(partial, context, options) {
if (options.hash) { if (options.hash) {
context = Utils.extend({}, context, options.hash); context = extend({}, context, options.hash);
if (options.ids) { if (options.ids) {
options.ids[0] = true; options.ids[0] = true;
} }
} }
partial = env.VM.resolvePartial.call(this, partial, context, options); partial = env.VM.resolvePartial.call(this, partial, context, options);
let extendedOptions = Utils.extend({}, options, { let optionsWithHooks = extend({}, options, {hooks: this.hooks});
hooks: this.hooks,
protoAccessControl: this.protoAccessControl
});
let result = env.VM.invokePartial.call( let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
this,
partial,
context,
extendedOptions
);
if (result == null && env.compile) { if (result == null && env.compile) {
options.partials[options.name] = env.compile( options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
partial, result = options.partials[options.name](context, optionsWithHooks);
templateSpec.compilerOptions,
env
);
result = options.partials[options.name](context, extendedOptions);
} }
if (result != null) { if (result != null) {
if (options.indent) { if (options.indent) {
@@ -108,11 +73,7 @@ export function template(templateSpec, env) {
} }
return result; return result;
} else { } else {
throw new Exception( throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
'The partial ' +
options.name +
' could not be compiled when running in runtime-only mode'
);
} }
} }
@@ -120,31 +81,14 @@ export function template(templateSpec, env) {
let container = { let container = {
strict: function(obj, name, loc) { strict: function(obj, name, loc) {
if (!obj || !(name in obj)) { if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, { throw new Exception('"' + name + '" not defined in ' + obj, { loc: loc });
loc: loc
});
} }
return obj[name]; return obj[name];
}, },
lookupProperty: function(parent, propertyName) {
let result = parent[propertyName];
if (result == null) {
return result;
}
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return result;
}
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
return result;
}
return undefined;
},
lookup: function(depths, name) { lookup: function(depths, name) {
const len = depths.length; const len = depths.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let result = depths[i] && container.lookupProperty(depths[i], name); if (depths[i] && depths[i][name] != null) {
if (result != null) {
return depths[i][name]; return depths[i][name];
} }
} }
@@ -153,7 +97,7 @@ export function template(templateSpec, env) {
return typeof current === 'function' ? current.call(context) : current; return typeof current === 'function' ? current.call(context) : current;
}, },
escapeExpression: Utils.escapeExpression, escapeExpression: escapeExpression,
invokePartial: invokePartialWrapper, invokePartial: invokePartialWrapper,
fn: function(i) { fn: function(i) {
@@ -167,15 +111,7 @@ export function template(templateSpec, env) {
let programWrapper = this.programs[i], let programWrapper = this.programs[i],
fn = this.fn(i); fn = this.fn(i);
if (data || depths || blockParams || declaredBlockParams) { if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram( programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
this,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
);
} else if (!programWrapper) { } else if (!programWrapper) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn); programWrapper = this.programs[i] = wrapProgram(this, i, fn);
} }
@@ -188,15 +124,6 @@ export function template(templateSpec, env) {
} }
return value; return value;
}, },
mergeIfNeeded: function(param, common) {
let obj = param || common;
if (param && common && param !== common) {
obj = Utils.extend({}, common, param);
}
return obj;
},
// An empty object to use as replacement for null-contexts // An empty object to use as replacement for null-contexts
nullContext: Object.seal({}), nullContext: Object.seal({}),
@@ -215,79 +142,44 @@ export function template(templateSpec, env) {
blockParams = templateSpec.useBlockParams ? [] : undefined; blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) { if (templateSpec.useDepths) {
if (options.depths) { if (options.depths) {
depths = depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
context != options.depths[0]
? [context].concat(options.depths)
: options.depths;
} else { } else {
depths = [context]; depths = [context];
} }
} }
function main(context/*, options*/) { function main(context/*, options*/) {
return ( return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
'' +
templateSpec.main(
container,
context,
container.helpers,
container.partials,
data,
blockParams,
depths
)
);
} }
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
main = executeDecorators(
templateSpec.main,
main,
container,
options.depths || [],
data,
blockParams
);
return main(context, options); return main(context, options);
} }
ret.isTop = true; ret.isTop = true;
ret._setup = function(options) { ret._setup = function(options) {
if (!options.partial) { if (!options.partial) {
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers); container.helpers = extend({}, env.helpers, options.helpers);
wrapHelpersToPassLookupProperty(mergedHelpers, container);
container.helpers = mergedHelpers;
if (templateSpec.usePartial) { if (templateSpec.usePartial) {
// Use mergeIfNeeded here to prevent compiling global partials multiple times container.partials = extend({}, env.partials, options.partials);
container.partials = container.mergeIfNeeded(
options.partials,
env.partials
);
} }
if (templateSpec.usePartial || templateSpec.useDecorators) { if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = Utils.extend( container.decorators = extend({}, env.decorators, options.decorators);
{},
env.decorators,
options.decorators
);
} }
container.hooks = {}; container.hooks = {};
container.protoAccessControl = createProtoAccessControl(options);
let keepHelperInHelpers = let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7;
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else { } else {
container.protoAccessControl = options.protoAccessControl; // internal option
container.helpers = options.helpers; container.helpers = options.helpers;
container.partials = options.partials; container.partials = options.partials;
container.decorators = options.decorators; container.decorators = options.decorators;
container.hooks = options.hooks; container.hooks = options.hooks;
} }
}; };
ret._child = function(i, data, blockParams, depths) { ret._child = function(i, data, blockParams, depths) {
@@ -298,47 +190,24 @@ export function template(templateSpec, env) {
throw new Exception('must pass parent depths'); throw new Exception('must pass parent depths');
} }
return wrapProgram( return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
container,
i,
templateSpec[i],
data,
0,
blockParams,
depths
);
}; };
return ret; return ret;
} }
export function wrapProgram( export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) { function prog(context, options = {}) {
let currentDepths = depths; let currentDepths = depths;
if ( if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
depths &&
context != depths[0] &&
!(context === container.nullContext && depths[0] === null)
) {
currentDepths = [context].concat(depths); currentDepths = [context].concat(depths);
} }
return fn( return fn(container,
container,
context, context,
container.helpers, container.helpers, container.partials,
container.partials,
options.data || data, options.data || data,
blockParams && [options.blockParams].concat(blockParams), blockParams && [options.blockParams].concat(blockParams),
currentDepths currentDepths);
);
} }
prog = executeDecorators(fn, prog, container, depths, data, blockParams); prog = executeDecorators(fn, prog, container, depths, data, blockParams);
@@ -380,10 +249,8 @@ export function invokePartial(partial, context, options) {
options.data = createFrame(options.data); options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure // Wrapper function to get access to currentPartialBlock from the closure
let fn = options.fn; let fn = options.fn;
partialBlock = options.data['partial-block'] = function partialBlockWrapper( partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
context,
options = {}
) {
// Restore the partial-block from the closure for the execution of the block // Restore the partial-block from the closure for the execution of the block
// i.e. the part inside the block of the partial call. // i.e. the part inside the block of the partial call.
options.data = createFrame(options.data); options.data = createFrame(options.data);
@@ -391,7 +258,7 @@ export function invokePartial(partial, context, options) {
return fn(context, options); return fn(context, options);
}; };
if (fn.partials) { if (fn.partials) {
options.partials = Utils.extend({}, options.partials, fn.partials); options.partials = extend({}, options.partials, fn.partials);
} }
} }
@@ -406,9 +273,7 @@ export function invokePartial(partial, context, options) {
} }
} }
export function noop() { export function noop() { return ''; }
return '';
}
function initData(context, data) { function initData(context, data) {
if (!data || !('root' in data)) { if (!data || !('root' in data)) {
@@ -421,30 +286,8 @@ function initData(context, data) {
function executeDecorators(fn, prog, container, depths, data, blockParams) { function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) { if (fn.decorator) {
let props = {}; let props = {};
prog = fn.decorator( prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
prog, extend(prog, props);
props,
container,
depths && depths[0],
data,
blockParams,
depths
);
Utils.extend(prog, props);
} }
return prog; return prog;
} }
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
Object.keys(mergedHelpers).forEach(helperName => {
let helper = mergedHelpers[helperName];
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
});
}
function passLookupPropertyOption(helper, container) {
const lookupProperty = container.lookupProperty;
return wrapHelper(helper, options => {
return Utils.extend({ lookupProperty }, options);
});
}
+22 -22
View File
@@ -1,3 +1,4 @@
const escape = { const escape = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
@@ -32,29 +33,27 @@ export let toString = Object.prototype.toString;
// Sourced from lodash // Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
/* eslint-disable func-style */ /* eslint-disable func-style */
let isFunction = function(value) { export const isFunction = createIsFunction();
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari function createIsFunction() {
/* istanbul ignore next */ // istanbul ignore next
if (isFunction(/x/)) { // eslint-disable-next-line no-constant-condition
isFunction = function(value) { if (typeof /x/ === 'function') {
return ( // fallback for older chrome/firefox versions
typeof value === 'function' && return function isFunction(value) {
toString.call(value) === '[object Function]' return typeof value === 'function' && toString.call(value) === '[object Function]';
);
}; };
} }
export { isFunction }; return function isFunction(value) {
/* eslint-enable func-style */ return typeof value === 'function';
};
}
/* istanbul ignore next */ /* istanbul ignore next */
export const isArray = export const isArray = Array.isArray || function(value) {
Array.isArray || return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
function(value) {
return value && typeof value === 'object'
? toString.call(value) === '[object Array]'
: false;
}; };
// Older IE versions do not directly support indexOf so we must implement our own, sadly. // Older IE versions do not directly support indexOf so we must implement our own, sadly.
@@ -67,6 +66,8 @@ export function indexOf(array, value) {
return -1; return -1;
} }
export function escapeExpression(string) { export function escapeExpression(string) {
if (typeof string !== 'string') { if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe // don't escape SafeStrings, since they're already safe
@@ -84,12 +85,11 @@ export function escapeExpression(string) {
string = '' + string; string = '' + string;
} }
if (!possible.test(string)) { if (!possible.test(string)) { return string; }
return string;
}
return string.replace(badChars, escapeChar); return string.replace(badChars, escapeChar);
} }
export function isEmpty(value) { export function isEmpty(value) {
if (!value && value !== 0) { if (!value && value !== 0) {
return true; return true;
+21 -63
View File
@@ -1,10 +1,11 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import Async from 'neo-async'; import Async from 'neo-async';
import fs from 'fs'; import fs from 'fs';
import * as Handlebars from './handlebars'; import Handlebars from './handlebars';
import {basename} from 'path'; import {basename} from 'path';
import {SourceMapConsumer, SourceNode} from 'source-map'; import {SourceMapConsumer, SourceNode} from 'source-map';
module.exports.loadTemplates = function(opts, callback) { module.exports.loadTemplates = function(opts, callback) {
loadStrings(opts, function(err, strings) { loadStrings(opts, function(err, strings) {
if (err) { if (err) {
@@ -26,17 +27,12 @@ function loadStrings(opts, callback) {
let strings = arrayCast(opts.string), let strings = arrayCast(opts.string),
names = arrayCast(opts.name); names = arrayCast(opts.name);
if (names.length !== strings.length && strings.length > 1) { if (names.length !== strings.length
return callback( && strings.length > 1) {
new Handlebars.Exception( return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
'Number of names did not match the number of string inputs'
)
);
} }
Async.map( Async.map(strings, function(string, callback) {
strings,
function(string, callback) {
if (string !== '-') { if (string !== '-') {
callback(undefined, string); callback(undefined, string);
} else { } else {
@@ -59,32 +55,22 @@ function loadStrings(opts, callback) {
source: string source: string
})); }));
callback(err, strings); callback(err, strings);
} });
);
} }
function loadFiles(opts, callback) { function loadFiles(opts, callback) {
// Build file extension pattern // Build file extension pattern
let extension = (opts.extension || 'handlebars').replace( let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; });
/[\\^$*+?.():=!|{}\-[\]]/g,
function(arg) {
return '\\' + arg;
}
);
extension = new RegExp('\\.' + extension + '$'); extension = new RegExp('\\.' + extension + '$');
let ret = [], let ret = [],
queue = (opts.files || []).map(template => ({ template, root: opts.root })); queue = (opts.files || []).map((template) => ({template, root: opts.root}));
Async.whilst( Async.whilst(() => queue.length, function(callback) {
() => queue.length,
function(callback) {
let {template: path, root} = queue.shift(); let {template: path, root} = queue.shift();
fs.stat(path, function(err, stat) { fs.stat(path, function(err, stat) {
if (err) { if (err) {
return callback( return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
} }
if (stat.isDirectory()) { if (stat.isDirectory()) {
@@ -98,10 +84,7 @@ function loadFiles(opts, callback) {
children.forEach(function(file) { children.forEach(function(file) {
let childPath = path + '/' + file; let childPath = path + '/' + file;
if ( if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({template: childPath, root: root || path}); queue.push({template: childPath, root: root || path});
} }
}); });
@@ -145,8 +128,7 @@ function loadFiles(opts, callback) {
} else { } else {
callback(undefined, ret); callback(undefined, ret);
} }
} });
);
} }
module.exports.cli = function(opts) { module.exports.cli = function(opts) {
@@ -156,9 +138,7 @@ module.exports.cli = function(opts) {
} }
if (!opts.templates.length && !opts.hasDirectory) { if (!opts.templates.length && !opts.hasDirectory) {
throw new Handlebars.Exception( throw new Handlebars.Exception('Must define at least one template or directory.');
'Must define at least one template or directory.'
);
} }
if (opts.simple && opts.min) { if (opts.simple && opts.min) {
@@ -167,18 +147,12 @@ module.exports.cli = function(opts) {
const multiple = opts.templates.length !== 1 || opts.hasDirectory; const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) { if (opts.simple && multiple) {
throw new Handlebars.Exception( throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
'Unable to output multiple templates in simple mode'
);
} }
// Force simple mode if we have only one template and it's unnamed. // Force simple mode if we have only one template and it's unnamed.
if ( if (!opts.amd && !opts.commonjs && opts.templates.length === 1
!opts.amd && && !opts.templates[0].name) {
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true; opts.simple = true;
} }
@@ -198,11 +172,7 @@ module.exports.cli = function(opts) {
let output = new SourceNode(); let output = new SourceNode();
if (!opts.simple) { if (!opts.simple) {
if (opts.amd) { if (opts.amd) {
output.add( output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
"define(['" +
opts.handlebarPath +
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
);
} else if (opts.commonjs) { } else if (opts.commonjs) {
output.add('var Handlebars = require("' + opts.commonjs + '");'); output.add('var Handlebars = require("' + opts.commonjs + '");');
} else { } else {
@@ -236,10 +206,7 @@ module.exports.cli = function(opts) {
// If we are generating a source map, we have to reconstruct the SourceNode object // If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) { if (opts.map) {
let consumer = new SourceMapConsumer(precompiled.map); let consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap( precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
precompiled.code,
consumer
);
} }
if (opts.simple) { if (opts.simple) {
@@ -252,14 +219,7 @@ module.exports.cli = function(opts) {
if (opts.amd && !multiple) { if (opts.amd && !multiple) {
output.add('return '); output.add('return ');
} }
output.add([ output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
objectName,
"['",
template.name,
"'] = template(",
precompiled,
');\n'
]);
} }
}); });
@@ -326,9 +286,7 @@ function minify(output, sourceMapFile) {
throw e; throw e;
} }
// it does not exist! // it does not exist!
console.error( console.error('Code minimization is disabled due to missing uglify-js dependency');
'Code minimization is disabled due to missing uglify-js dependency'
);
return output; return output;
} }
return require('uglify-js').minify(output.code, { return require('uglify-js').minify(output.code, {
-9
View File
@@ -1,9 +0,0 @@
module.exports = {
'check-coverage': true,
branches: 100,
lines: 100,
functions: 100,
statements: 100,
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
reporter: 'html'
};
+5406 -3627
View File
File diff suppressed because it is too large Load Diff
+17 -42
View File
@@ -1,7 +1,7 @@
{ {
"name": "handlebars", "name": "handlebars",
"barename": "handlebars", "barename": "handlebars",
"version": "4.7.0", "version": "4.5.3",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"homepage": "http://www.handlebarsjs.com/", "homepage": "http://www.handlebarsjs.com/",
"keywords": [ "keywords": [
@@ -29,49 +29,46 @@
"uglify-js": "^3.1.4" "uglify-js": "^3.1.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.4",
"@babel/plugin-transform-modules-amd": "^7.7.4",
"@babel/plugin-transform-modules-commonjs": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@babel/runtime": "^7.7.4",
"@knappi/grunt-saucelabs": "^9.0.2", "@knappi/grunt-saucelabs": "^9.0.2",
"aws-sdk": "^2.1.49", "aws-sdk": "^2.1.49",
"babel-loader": "^5.0.0", "babel-loader": "^8.0.6",
"babel-runtime": "^5.1.10", "babel-plugin-add-module-exports": "^1.0.2",
"benchmark": "~1.0", "benchmark": "~1.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-diff": "^1.0.1",
"concurrently": "^5.0.0",
"dirty-chai": "^2.0.1", "dirty-chai": "^2.0.1",
"dtslint": "^0.5.5", "dtslint": "^0.5.5",
"dustjs-linkedin": "^2.0.2", "dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3", "eco": "~1.1.0-rc-3",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-compat": "^3.3.0", "eslint-plugin-compat": "^3.3.0",
"eslint-plugin-es5": "^1.4.1", "eslint-plugin-es5": "^1.4.1",
"fs-extra": "^8.1.0",
"grunt": "^1.0.4", "grunt": "^1.0.4",
"grunt-babel": "^5.0.0", "grunt-babel": "^8.0.0",
"grunt-bg-shell": "^2.3.3", "grunt-bg-shell": "^2.3.3",
"grunt-cli": "^1", "grunt-cli": "^1",
"grunt-contrib-clean": "^1", "grunt-contrib-clean": "^1",
"grunt-contrib-concat": "^1", "grunt-contrib-concat": "^1",
"grunt-contrib-connect": "^1", "grunt-contrib-connect": "^1",
"grunt-contrib-copy": "^1", "grunt-contrib-copy": "^1",
"grunt-contrib-requirejs": "^1",
"grunt-contrib-uglify": "^1",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-webpack": "^1.0.8", "grunt-eslint": "^20.1.0",
"husky": "^3.1.0", "grunt-webpack": "^3.1.3",
"istanbul": "^0.3.0",
"jison": "~0.3.0", "jison": "~0.3.0",
"lint-staged": "^9.5.0",
"mocha": "^5", "mocha": "^5",
"mock-stdin": "^0.3.0", "mock-stdin": "^0.3.0",
"mustache": "^2.1.3", "mustache": "^2.1.3",
"nyc": "^14.1.1",
"prettier": "^1.19.1",
"semver": "^5.0.1", "semver": "^5.0.1",
"sinon": "^7.5.0", "sinon": "^7.5.0",
"typescript": "^3.4.3", "typescript": "^3.4.3",
"underscore": "^1.5.1", "underscore": "^1.5.1",
"webpack": "^1.12.6", "webpack": "^4.41.2",
"webpack-dev-server": "^1.12.1" "webpack-dev-server": "^3.9.0"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
@@ -83,15 +80,8 @@
"handlebars": "bin/handlebars" "handlebars": "bin/handlebars"
}, },
"scripts": { "scripts": {
"format": "prettier --write '**/*.js' && eslint --fix .", "checkTypes": "dtslint types",
"check-format": "prettier --check '**/*.js'", "test": "grunt"
"lint": "eslint --max-warnings 0 .",
"dtslint": "dtslint types",
"test": "grunt",
"extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",
"integration-test": "grunt integration-tests",
"--- combined tasks ---": "",
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:dtslint npm:check-format npm:test"
}, },
"jspm": { "jspm": {
"main": "handlebars", "main": "handlebars",
@@ -112,20 +102,5 @@
"release-notes.md", "release-notes.md",
"runtime.js", "runtime.js",
"types/*.d.ts" "types/*.d.ts"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,css,json,md}": [
"prettier --write",
"git add"
],
"*.js": [
"eslint --fix",
"git add"
] ]
} }
}
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
tabWidth: 2,
semi: true,
singleQuote: true
};
+35 -146
View File
@@ -2,68 +2,15 @@
## Development ## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v4.7.0...master) [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...master)
## v4.7.0 - January 10th, 2020
Features:
- feat: default options for controlling proto access - 7af1c12, #1635
- This makes it possible to disable the prototype access restrictions added in 4.6.0
- an error is logged in the console, if access to prototype properties is attempted and denied
and no explicit configuration has taken place.
Compatibility notes:
- no compatibilities are expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.6.0...v4.7.0)
## v4.6.0 - January 8th, 2020
Features:
- feat: access control to prototype properties via whitelist (#1633)- d03b6ec
Bugfixes:
- fix(runtime.js): partials compile not caching (#1600) - 23d58e7
Chores, docs:
- various refactorings and improvements to tests - d7f0dcf, 187d611, d337f40
- modernize the build-setup
- use prettier to format and eslint to verify - c40d9f3, 8901c28, e97685e, 1f61f21
- use nyc instead of istanbul to collect coverage - 164b7ff, 1ebce2b
- update build code to use modern javascript and make it cleaner - 14b621c, 1ec1737, 3a5b65e, dde108e, 04b1984, 587e7a3
- restructur build commands - e913dc5,
- eslint rule changes - ac4655e, dc54952
- Update (C) year in the LICENSE file - d1fb07b
- chore: try to fix saucelabs credentials (#1627) -
- Update readme.md with updated links (#1620) - edcc84f
BREAKING CHANGES:
- access to prototype properties is forbidden completely by default,
specific properties or methods can be allow via runtime-options.
See #1633 for details.
If you are using Handlebars as documented, you should not be accessing prototype
properties from your template anyway, so the changes should not be a problem
for you. Only the use of undocumented features can break your build.
That is why we only bump the minor version despite mentioning breaking changes
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...v4.6.0)
## v4.5.3 - November 18th, 2019 ## v4.5.3 - November 18th, 2019
Bugfixes: Bugfixes:
- fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7 - fix: add "no-prototype-builtins" eslint-rule and fix all occurences - f7f05d7
- fix: add more properties required to be enumerable - 1988878 - fix: add more properties required to be enumerable - 1988878
Chores / Build: Chores / Build:
- fix: use !== 0 instead of != 0 - c02b05f - fix: use !== 0 instead of != 0 - c02b05f
- add chai and dirty-chai and sinon, for cleaner test-assertions and spies, - add chai and dirty-chai and sinon, for cleaner test-assertions and spies,
deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0 deprecate old assertion-methods - 93e284e, 886ba86, 0817dad, 93516a0
@@ -86,7 +33,7 @@ Compatibility notes:
```js ```js
{ {
__proto__: 'some string'; __proto__: 'some string'
} }
``` ```
@@ -95,145 +42,133 @@ Compatibility notes:
are not intended, undocumented and far less important than fixing are not intended, undocumented and far less important than fixing
Remote-Code-Execution exploits on existing systems. Remote-Code-Execution exploits on existing systems.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...v4.5.3) [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.2...v4.5.3)
## v4.5.2 - November 13th, 2019 ## v4.5.2 - November 13th, 2019
# Bugfixes # Bugfixes
- fix: use String(field) in lookup when checking for "constructor" - d541378 - fix: use String(field) in lookup when checking for "constructor" - d541378
- test: add fluent API for testing Handlebars - c2ac79c - test: add fluent API for testing Handlebars - c2ac79c
Compatibility notes: Compatibility notes:
- no incompatibility are to be expected - no incompatibility are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.1...v4.5.2) [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.1...v4.5.2)
## v4.5.1 - October 29th, 2019 ## v4.5.1 - October 29th, 2019
Bugfixs Bugfixs
- fix: move "eslint-plugin-compat" to devDependencies - 5e9d17f (#1589) - fix: move "eslint-plugin-compat" to devDependencies - 5e9d17f (#1589)
Compatibility notes: Compatibility notes:
- No compatibility issues are to be expected - No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.5.0...v4.5.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.0...v4.5.1)
## v4.5.0 - October 28th, 2019 ## v4.5.0 - October 28th, 2019
Features / Improvements Features / Improvements
- Add method Handlebars.parseWithoutProcessing (#1584) - 62ed3c2 - Add method Handlebars.parseWithoutProcessing (#1584) - 62ed3c2
- add guard to if & unless helpers (#1549) - add guard to if & unless helpers (#1549)
- show source location for the strict lookup exceptions - feb60f8 - show source location for the strict lookup exceptions - feb60f8
Bugfixes: Bugfixes:
- Use objects for hash value tracking - 7fcf9d2 - Use objects for hash value tracking - 7fcf9d2
Chore: Chore:
- Resolve deprecation warning message from eslint while running eslint (#1586) - 7052e88 - Resolve deprecation warning message from eslint while running eslint (#1586) - 7052e88
- chore: add eslint-plugin-compat and eslint-plugin-es5 - 088e618 - chore: add eslint-plugin-compat and eslint-plugin-es5 - 088e618
Compatibility notes: Compatibility notes:
- No compatibility issues are to be expected - No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...v4.5.0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.5...v4.5.0)
## v4.4.5 - October 20th, 2019 ## v4.4.5 - October 20th, 2019
Bugfixes: Bugfixes:
- Contents of raw-blocks must be matched with non-eager regex-matching - 8d5530e, #1579 - Contents of raw-blocks must be matched with non-eager regex-matching - 8d5530e, #1579
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.4...v4.4.5) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.4...v4.4.5)
## v4.4.4 - October 20th, 2019 ## v4.4.4 - October 20th, 2019
Bugfixes: Bugfixes:
- fix: prevent zero length tokens in raw-blocks (#1577, #1578) - f1752fe - fix: prevent zero length tokens in raw-blocks (#1577, #1578) - f1752fe
Chore: Chore:
- chore: link to s3 bucket with https, add "npm ci" to build instructions - 0b593bf - chore: link to s3 bucket with https, add "npm ci" to build instructions - 0b593bf
Compatibility notes: Compatibility notes:
- no compatibility issues are expected - no compatibility issues are expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.3...v4.4.4) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.3...v4.4.4)
## v4.4.3 - October 8th, 2019 ## v4.4.3 - October 8th, 2019
Bugfixes Bugfixes
Typings: Typings:
- add missing type fields to AST typings and add tests for them - 0440af2 - add missing type fields to AST typings and add tests for them - 0440af2
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.2...v4.4.3) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.2...v4.4.3)
## v4.4.2 - October 2nd, 2019 ## v4.4.2 - October 2nd, 2019
- chore: fix grunt-saucelabs dependency - b7eada0 - chore: fix grunt-saucelabs dependency - b7eada0
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.1...v4.4.2) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.1...v4.4.2)
## v4.4.1 - October 2nd, 2019 ## v4.4.1 - October 2nd, 2019
- [#1562](https://github.com/wycats/handlebars.js/issues/1562) - Error message for syntax error missing location in 4.2.1+ - [#1562](https://github.com/wycats/handlebars.js/issues/1562) - Error message for syntax error missing location in 4.2.1+
[Commits](https://github.com/wycats/handlebars.js/compare/v4.4.0...v4.4.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.0...v4.4.1)
## v4.4.0 - September 29th, 2019 ## v4.4.0 - September 29th, 2019
- Added support for iterable objects in {{#each}} helper (#1557) - cf7545e - Added support for iterable objects in {{#each}} helper (#1557) - cf7545e
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.4...v4.4.0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.4...v4.4.0)
## v4.3.4 - September 28th, 2019 ## v4.3.4 - September 28th, 2019
- fix: harden "propertyIsEnumerable"-check - ff4d827 - fix: harden "propertyIsEnumerable"-check - ff4d827
Compatibility notes: Compatibility notes:
- No incompatibilities are known. - No incompatibilities are known.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.3...v4.3.4) [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.3...v4.3.4)
## v4.3.3 - September 27th, 2019 ## v4.3.3 - September 27th, 2019
- fix test case for browsers that do not support __defineGetter__ - 8742bde
- fix test case for browsers that do not support **defineGetter** - 8742bde
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.2...v4.3.3) [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.2...v4.3.3)
## v4.3.2 - September 26th, 2019 ## v4.3.2 - September 26th, 2019
- Use Object.prototype.propertyIsEnumerable to check for constructors - 213c0bb, #1563 - Use Object.prototype.propertyIsEnumerable to check for constructors - 213c0bb, #1563
Compatibility notes: Compatibility notes:
- There are no breaking changes - There are no breaking changes
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.1...v4.3.2) [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.1...v4.3.2)
## v4.3.1 - September 25th, 2019 ## v4.3.1 - September 25th, 2019
Fixes: Fixes:
- do not break on precompiled templates from Handlebars >=4.0.0 <4.3.0 - 1266838, #1561 - do not break on precompiled templates from Handlebars >=4.0.0 <4.3.0 - 1266838, #1561
- Ensure allowCallsToHelperMissing runtime option is optional in typings - 93444c5, 64ecb9e, #1560 - Ensure allowCallsToHelperMissing runtime option is optional in typings - 93444c5, 64ecb9e, #1560
[Commits](https://github.com/wycats/handlebars.js/compare/v4.3.0...v4.3.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.0...v4.3.1)
## v4.3.0 - September 24th, 2019 ## v4.3.0 - September 24th, 2019
Fixes: Fixes:
- Security: Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72 - Security: Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72
@@ -246,9 +181,7 @@ Features:
Breaking changes: Breaking changes:
Compatibility notes: Compatibility notes:
- Compiler revision increased - 06b7224 - Compiler revision increased - 06b7224
- This means that template compiled with versions prior to 4.3.0 will not work with runtimes >= 4.3.0 - This means that template compiled with versions prior to 4.3.0 will not work with runtimes >= 4.3.0
The increase was done because the "helperMissing" and "blockHelperMissing" are now moved from the helpers The increase was done because the "helperMissing" and "blockHelperMissing" are now moved from the helpers
to the internal "container.hooks" object, so old templates will not be able to call them anymore. We suggest to the internal "container.hooks" object, so old templates will not be able to call them anymore. We suggest
@@ -257,9 +190,9 @@ Compatibility notes:
- Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72 - Disallow calling "helperMissing" and "blockHelperMissing" directly - 2078c72
- Calling "helperMissing" and "blockHelperMissing" directly from a template (like in `{{blockHelperMissing}}` was - Calling "helperMissing" and "blockHelperMissing" directly from a template (like in `{{blockHelperMissing}}` was
never intended and was part of the exploits that have been revealed early in 2019 never intended and was part of the exploits that have been revealed early in 2019
(see https://github.com/wycats/handlebars.js/issues/1495). _It is also part of a new exploit that (see https://github.com/wycats/handlebars.js/issues/1495). *It is also part of a new exploit that
is not captured by the earlier fix._ In order to harden Handlebars against such exploits, calling thos helpers is not captured by the earlier fix.* In order to harden Handlebars against such exploits, calling thos helpers
is now not possible anymore. _Overriding_ those helpers is still possible. is now not possible anymore. *Overriding* those helpers is still possible.
- If you really need this behavior, you can set the runtime option `allowCallsToHelperMissing` to `true` and the - If you really need this behavior, you can set the runtime option `allowCallsToHelperMissing` to `true` and the
calls will again be possible calls will again be possible
@@ -267,64 +200,57 @@ Both bullet points imly that Handlebars is not 100% percent compatible to 4.2.0,
We consider it more important to resolve a major security issue than to maintain 100% compatibility. We consider it more important to resolve a major security issue than to maintain 100% compatibility.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.2.1...v4.3.0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.2.1...v4.3.0)
## v4.2.1 - September 20th, 2019 ## v4.2.1 - September 20th, 2019
Bugfixes: Bugfixes:
- The "browser" property in the package.json has been updated to use the common-js builds instead of the minified UMD - c55a7be, #1553 - The "browser" property in the package.json has been updated to use the common-js builds instead of the minified UMD - c55a7be, #1553
Compatibility notes: Compatibility notes:
- No compatibility issues should arise - No compatibility issues should arise
[Commits](https://github.com/wycats/handlebars.js/compare/v4.2.0...v4.2.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.2.0...v4.2.1)
## v4.2.0 - September 3rd, 2019 ## v4.2.0 - September 3rd, 2019
Chore/Test: Chore/Test:
- Use custom `grunt-saucelab` with current sauce-connect proxy - f119497 - Use custom `grunt-saucelab` with current sauce-connect proxy - f119497
- Add framework for various integration tests - f9cce4d - Add framework for various integration tests - f9cce4d
- Add integration test for webpack - a57b682 - Add integration test for webpack - a57b682
Bugfixes:
Bugfixes:
- [#1544](https://github.com/wycats/handlebars.js/issues/1544) - Typescript types: `knownHelpers` doesnt allow for custom helpers ([@NickCis](https://api.github.com/users/NickCis)) - [#1544](https://github.com/wycats/handlebars.js/issues/1544) - Typescript types: `knownHelpers` doesnt allow for custom helpers ([@NickCis](https://api.github.com/users/NickCis))
- [#1534](https://github.com/wycats/handlebars.js/pull/1534) - Add typings for "Handlebars.VM.resolvePartial ([@AndrewLeedham](https://api.github.com/users/AndrewLeedham)) - [#1534](https://github.com/wycats/handlebars.js/pull/1534) - Add typings for "Handlebars.VM.resolvePartial ([@AndrewLeedham](https://api.github.com/users/AndrewLeedham))
Features: Features:
- [#1540](https://github.com/wycats/handlebars.js/pull/1540) - added "browser"-property to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan)) - [#1540](https://github.com/wycats/handlebars.js/pull/1540) - added "browser"-property to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan))
Compatibility notes: Compatibility notes:
- The new "browser"-property should not break anything, but you can never be sure. The integration test for webpack - The new "browser"-property should not break anything, but you can never be sure. The integration test for webpack
shows that it works, but if it doesn't please open an issue. shows that it works, but if it doesn't please open an issue.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2-0...v4.2.0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2-0...v4.2.0)
## v4.1.2-0 - August 25th, 2019 ## v4.1.2-0 - August 25th, 2019
[#1540](https://github.com/wycats/handlebars.js/pull/1540) - added browser to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan)) [#1540](https://github.com/wycats/handlebars.js/pull/1540) - added browser to package.json, resolves #1102 ([@ouijan](https://api.github.com/users/ouijan))
Compatibility notes: Compatibility notes:
- We are not sure if imports via webpack are still working, which is why this release is a pre-release - We are not sure if imports via webpack are still working, which is why this release is a pre-release
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.1.2-0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.1.2-0)
## v4.1.2 - April 13th, 2019 ## v4.1.2 - April 13th, 2019
Chore/Test: Chore/Test:
- [#1515](https://github.com/wycats/handlebars.js/pull/1515) - Port over linting and test for typings ([@zimmi88](https://api.github.com/users/zimmi88)) - [#1515](https://github.com/wycats/handlebars.js/pull/1515) - Port over linting and test for typings ([@zimmi88](https://api.github.com/users/zimmi88))
- chore: add missing typescript dependency, add package-lock.json - 594f1e3 - chore: add missing typescript dependency, add package-lock.json - 594f1e3
- test: remove safari from saucelabs - 871accc - test: remove safari from saucelabs - 871accc
Bugfixes: Bugfixes:
- fix: prevent RCE through the "lookup"-helper - cd38583 - fix: prevent RCE through the "lookup"-helper - cd38583
Compatibility notes: Compatibility notes:
@@ -340,24 +266,20 @@ and the issue may not be resolved on many systems).
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.1...v4.1.2) [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.1...v4.1.2)
## v4.1.1 - March 16th, 2019 ## v4.1.1 - March 16th, 2019
Bugfixes: Bugfixes:
- fix: add "runtime.d.ts" to allow "require('handlebars/runtime')" in TypeScript - 5cedd62 - fix: add "runtime.d.ts" to allow "require('handlebars/runtime')" in TypeScript - 5cedd62
Refactorings: Refactorings:
- replace "async" with "neo-async" - 048f2ce - replace "async" with "neo-async" - 048f2ce
- use "substring"-function instead of "substr" - 445ae12 - use "substring"-function instead of "substr" - 445ae12
Compatibility notes: Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features. - This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.1.0...v4.1.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.0...v4.1.1)
## v4.1.0 - February 7th, 2019 ## v4.1.0 - February 7th, 2019
New Features New Features
- import TypeScript typings - 27ac1ee - import TypeScript typings - 27ac1ee
@@ -390,10 +312,11 @@ document.getElementById('output').innerHTML = template(new SomeClass());
This kind of access is not the intended use of Handlebars and leads to the vulnerability described in #1495. We will **not** increase the major version, because such use is not intended or documented, and because of the potential impact of the issue (we fear that most people won't use a new major version and the issue may not be resolved on many systems). This kind of access is not the intended use of Handlebars and leads to the vulnerability described in #1495. We will **not** increase the major version, because such use is not intended or documented, and because of the potential impact of the issue (we fear that most people won't use a new major version and the issue may not be resolved on many systems).
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.1.0) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.12...v4.1.0)
## v4.0.12 - September 4th, 2018 ## v4.0.12 - September 4th, 2018
New features: New features:
- none - none
@@ -418,27 +341,25 @@ Removed obsolete code:
- Update jsfiddle to 4.0.11 - 8947dd0 - Update jsfiddle to 4.0.11 - 8947dd0
Compatibility notes: Compatibility notes:
- No compatibility issues are to be expected - No compatibility issues are to be expected
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...v4.0.12) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.11...v4.0.12)
## v4.0.11 - October 17th, 2017 ## v4.0.11 - October 17th, 2017
- [#1391](https://github.com/wycats/handlebars.js/issues/1391) - `uglify-js` is unconditionally imported, but only listed as optional dependency ([@Turbo87](https://github.com/Turbo87)) - [#1391](https://github.com/wycats/handlebars.js/issues/1391) - `uglify-js` is unconditionally imported, but only listed as optional dependency ([@Turbo87](https://github.com/Turbo87))
- [#1233](https://github.com/wycats/handlebars.js/issues/1233) - Unable to build under windows - error at test:bin task ([@blikblum](https://github.com/blikblum)) - [#1233](https://github.com/wycats/handlebars.js/issues/1233) - Unable to build under windows - error at test:bin task ([@blikblum](https://github.com/blikblum))
- Update (C) year in the LICENSE file - 21386b6 - Update (C) year in the LICENSE file - 21386b6
Compatibility notes: Compatibility notes:
- This is a bugfix release. There are no breaking change and no new features. - This is a bugfix release. There are no breaking change and no new features.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.0.11) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.0.11)
## v4.0.10 - May 21st, 2017 ## v4.0.10 - May 21st, 2017
- Fix regression in 4.0.9: Replace "Object.assign" (not support in IE) by "util/extend" - 0e953d1 - Fix regression in 4.0.9: Replace "Object.assign" (not support in IE) by "util/extend" - 0e953d1
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.9...v4.0.10) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.9...v4.0.10)
## v4.0.9 - May 21st, 2017 ## v4.0.9 - May 21st, 2017
@@ -446,31 +367,27 @@ Compatibility notes:
- [#1327](https://github.com/wycats/handlebars.js/issues/1327) Handlebars.compile() does not modify "options" anymore - [#1327](https://github.com/wycats/handlebars.js/issues/1327) Handlebars.compile() does not modify "options" anymore
- pending [#1331](https://github.com/wycats/handlebars.js/issues/1331) Attempts to build Handlebars in a Windows environment - pending [#1331](https://github.com/wycats/handlebars.js/issues/1331) Attempts to build Handlebars in a Windows environment
- Fix build in windows - cc554a5 - Fix build in windows - cc554a5
- Ensure LF line-edings in handlebars-template fixtures (\*.hbs) - ed879a6 - Ensure LF line-edings in handlebars-template fixtures (*.hbs) - ed879a6
- Run integration test with `node handlebars -a ...` on Windows - 2e21e2b - Run integration test with `node handlebars -a ...` on Windows - 2e21e2b
- Ensure LF line-edings in lexer-files (\*.l) - bdfdbea - Ensure LF line-edings in lexer-files (*.l) - bdfdbea
- Force LF line-endings for spec/artifacts - b50ef03 - Force LF line-endings for spec/artifacts - b50ef03
- Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul - 6e6269f - Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul - 6e6269f
- TravisCI: Publish valid semver tags independently of the branch - 7378f85 - TravisCI: Publish valid semver tags independently of the branch - 7378f85
Compatibility notes: Compatibility notes:
- No compatibility issues are expected. - No compatibility issues are expected.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.8...v4.0.9) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.8...v4.0.9)
## v4.0.8 - May 2nd, 2017 ## v4.0.8 - May 2nd, 2017
- [#1341](https://github.com/wycats/handlebars.js/issues/1341) [#1342](https://github.com/wycats/handlebars.js/issues/1342) Allow partial-blocks to be executed without "options" ([@nknapp](https://github.com/nknapp)) - a00c598 - [#1341](https://github.com/wycats/handlebars.js/issues/1341) [#1342](https://github.com/wycats/handlebars.js/issues/1342) Allow partial-blocks to be executed without "options" ([@nknapp](https://github.com/nknapp)) - a00c598
Compatibility notes: Compatibility notes:
- No breaking changes - No breaking changes
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.7...v4.0.8) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.7...v4.0.8)
## v4.0.7 - April 29th, 2017 ## v4.0.7 - April 29th, 2017
- [#1319](https://github.com/wycats/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57 - [#1319](https://github.com/wycats/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57
- [#1315](https://github.com/wycats/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375 - [#1315](https://github.com/wycats/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375
- [#1290](https://github.com/wycats/handlebars.js/pull/1290) [#1252](https://github.com/wycats/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c - [#1290](https://github.com/wycats/handlebars.js/pull/1290) [#1252](https://github.com/wycats/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c
@@ -483,7 +400,6 @@ Compatibility notes:
[Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...v4.0.7) [Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...v4.0.7)
## v4.0.6 - November 12th, 2016 ## v4.0.6 - November 12th, 2016
- [#1243](https://github.com/wycats/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea)) - [#1243](https://github.com/wycats/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea))
- [#1210](https://github.com/wycats/handlebars.js/pull/1210) - Add a new lightweight package based on handlebars in the README ([@kabirbaidhya](https://github.com/kabirbaidhya)) - [#1210](https://github.com/wycats/handlebars.js/pull/1210) - Add a new lightweight package based on handlebars in the README ([@kabirbaidhya](https://github.com/kabirbaidhya))
- [#1187](https://github.com/wycats/handlebars.js/pull/1187) - Ensure that existing blockParams and depths are respected on dupe programs ([@charleso](https://github.com/charleso)) - [#1187](https://github.com/wycats/handlebars.js/pull/1187) - Ensure that existing blockParams and depths are respected on dupe programs ([@charleso](https://github.com/charleso))
@@ -505,7 +421,6 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.5...v4.0.6) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.5...v4.0.6)
## v4.0.5 - November 19th, 2015 ## v4.0.5 - November 19th, 2015
- [#1132](https://github.com/wycats/handlebars.js/pull/1132) - Update uglify-js to avoid vulnerability ([@plynchnlm](https://github.com/plynchnlm)) - [#1132](https://github.com/wycats/handlebars.js/pull/1132) - Update uglify-js to avoid vulnerability ([@plynchnlm](https://github.com/plynchnlm))
- [#1129](https://github.com/wycats/handlebars.js/issues/1129) - Minified lib returns an empty string ([@bricss](https://github.com/bricss)) - [#1129](https://github.com/wycats/handlebars.js/issues/1129) - Minified lib returns an empty string ([@bricss](https://github.com/bricss))
- Return current handlebars instance from noConflict - 685cf92 - Return current handlebars instance from noConflict - 685cf92
@@ -518,14 +433,12 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.4...v4.0.5) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.4...v4.0.5)
## v4.0.4 - October 29th, 2015 ## v4.0.4 - October 29th, 2015
- [#1121](https://github.com/wycats/handlebars.js/pull/1121) - Include partial name in 'undefined partial' exception message ([@shinypb](https://github.com/shinypb)) - [#1121](https://github.com/wycats/handlebars.js/pull/1121) - Include partial name in 'undefined partial' exception message ([@shinypb](https://github.com/shinypb))
- [#1125](https://github.com/wycats/handlebars.js/pull/1125) - Add promised-handlebars to "in-the-wild"-list ([@nknapp](https://github.com/nknapp)) - [#1125](https://github.com/wycats/handlebars.js/pull/1125) - Add promised-handlebars to "in-the-wild"-list ([@nknapp](https://github.com/nknapp))
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.3...v4.0.4) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.3...v4.0.4)
## v4.0.3 - September 23rd, 2015 ## v4.0.3 - September 23rd, 2015
- [#1099](https://github.com/wycats/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://github.com/btmorex)) - [#1099](https://github.com/wycats/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://github.com/btmorex))
- [#1093](https://github.com/wycats/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://github.com/florianpilz)) - [#1093](https://github.com/wycats/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://github.com/florianpilz))
- [#1092](https://github.com/wycats/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://github.com/distantnative)) - [#1092](https://github.com/wycats/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://github.com/distantnative))
@@ -533,26 +446,22 @@ Compatibility notes:
- [#1090](https://github.com/wycats/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://github.com/nikolas)) - [#1090](https://github.com/wycats/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://github.com/nikolas))
Compatibility notes: Compatibility notes:
- `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode. - `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode.
- `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values. - `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values.
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...v4.0.3) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...v4.0.3)
## v4.0.2 - September 4th, 2015 ## v4.0.2 - September 4th, 2015
- [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://github.com/michaellopez)) - [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://github.com/michaellopez))
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.1...v4.0.2) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.1...v4.0.2)
## v4.0.1 - September 2nd, 2015 ## v4.0.1 - September 2nd, 2015
- Fix failure when using decorators in partials - 05b82a2 - Fix failure when using decorators in partials - 05b82a2
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.0...v4.0.1) [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.0...v4.0.1)
## v4.0.0 - September 1st, 2015 ## v4.0.0 - September 1st, 2015
- [#1082](https://github.com/wycats/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://github.com/kpdecker)) - [#1082](https://github.com/wycats/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://github.com/kpdecker))
- [#1076](https://github.com/wycats/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://github.com/kpdecker)) - [#1076](https://github.com/wycats/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://github.com/kpdecker))
- [#1087](https://github.com/wycats/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://github.com/denniskuczynski)) - [#1087](https://github.com/wycats/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://github.com/denniskuczynski))
@@ -588,7 +497,6 @@ Compatibility notes:
- Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa) - Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa)
Compatibility notes: Compatibility notes:
- Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028). - Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028).
- The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces. - The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces.
- AST constructors have been dropped in favor of plain old javascript objects - AST constructors have been dropped in favor of plain old javascript objects
@@ -597,13 +505,11 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...v4.0.0) [Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...v4.0.0)
## v3.0.3 - April 28th, 2015 ## v3.0.3 - April 28th, 2015
- [#1004](https://github.com/wycats/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://github.com/boskee)) - [#1004](https://github.com/wycats/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://github.com/boskee))
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.2...v3.0.3) [Commits](https://github.com/wycats/handlebars.js/compare/v3.0.2...v3.0.3)
## v3.0.2 - April 20th, 2015 ## v3.0.2 - April 20th, 2015
- [#998](https://github.com/wycats/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://github.com/kpdecker)) - [#998](https://github.com/wycats/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://github.com/kpdecker))
- [#994](https://github.com/wycats/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://github.com/tamlyn)) - [#994](https://github.com/wycats/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://github.com/tamlyn))
- [#990](https://github.com/wycats/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://github.com/blimmer)) - [#990](https://github.com/wycats/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://github.com/blimmer))
@@ -615,7 +521,6 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.1...v3.0.2) [Commits](https://github.com/wycats/handlebars.js/compare/v3.0.1...v3.0.2)
## v3.0.1 - March 24th, 2015 ## v3.0.1 - March 24th, 2015
- [#984](https://github.com/wycats/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://github.com/johneke)) - [#984](https://github.com/wycats/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://github.com/johneke))
- [#973](https://github.com/wycats/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://github.com/elover)) - [#973](https://github.com/wycats/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://github.com/elover))
- [#966](https://github.com/wycats/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://github.com/abloomston)) - [#966](https://github.com/wycats/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://github.com/abloomston))
@@ -625,7 +530,6 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.0...v3.0.1) [Commits](https://github.com/wycats/handlebars.js/compare/v3.0.0...v3.0.1)
## v3.0.0 - February 10th, 2015 ## v3.0.0 - February 10th, 2015
- [#941](https://github.com/wycats/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://github.com/kpdecker)) - [#941](https://github.com/wycats/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://github.com/kpdecker))
- [#940](https://github.com/wycats/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://github.com/mattflaschen)) - [#940](https://github.com/wycats/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://github.com/mattflaschen))
- [#938](https://github.com/wycats/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://github.com/diwo)) - [#938](https://github.com/wycats/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://github.com/diwo))
@@ -655,7 +559,6 @@ Compatibility notes:
- Lookup partials when undefined - 617dd57 - Lookup partials when undefined - 617dd57
Compatibility notes: Compatibility notes:
- Runtime breaking changes. Must match 3.x runtime and precompiler. - Runtime breaking changes. Must match 3.x runtime and precompiler.
- The AST has been upgraded to a public API. - The AST has been upgraded to a public API.
- There are a number of changes to this, but the format is now documented in docs/compiler-api.md - There are a number of changes to this, but the format is now documented in docs/compiler-api.md
@@ -665,7 +568,6 @@ Compatibility notes:
- `SafeString` is now duck typed on `toHTML` - `SafeString` is now duck typed on `toHTML`
New Features: New Features:
- noConflict - noConflict
- Source Maps - Source Maps
- Block Params - Block Params
@@ -678,7 +580,6 @@ New Features:
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...v3.0.0) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...v3.0.0)
## v2.0.0 - September 1st, 2014 ## v2.0.0 - September 1st, 2014
- Update jsfiddle to 2.0.0-beta.1 - 0670f65 - Update jsfiddle to 2.0.0-beta.1 - 0670f65
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c - Add contrib note regarding handlebarsjs.com docs - 4d17e3c
- Play nice with gemspec version numbers - 64d5481 - Play nice with gemspec version numbers - 64d5481
@@ -686,7 +587,6 @@ New Features:
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
## v2.0.0-beta.1 - August 26th, 2014 ## v2.0.0-beta.1 - August 26th, 2014
- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://github.com/kpdecker)) - [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://github.com/kpdecker))
- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://github.com/scoot557)) - [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://github.com/scoot557))
- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://github.com/evensoul)) - [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://github.com/evensoul))
@@ -709,7 +609,6 @@ New Features:
- Fix compiler program de-duping - 9e3f824 - Fix compiler program de-duping - 9e3f824
Compatibility notes: Compatibility notes:
- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments. - The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time. - Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups. - `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
@@ -731,13 +630,11 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
## v2.0.0-alpha.4 - May 19th, 2014 ## v2.0.0-alpha.4 - May 19th, 2014
- Expose setup wrappers for compiled templates - 3638874 - Expose setup wrappers for compiled templates - 3638874
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4)
## v2.0.0-alpha.3 - May 19th, 2014 ## v2.0.0-alpha.3 - May 19th, 2014
- [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://github.com/tomdale)) - [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://github.com/tomdale))
- [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://github.com/mmun)) - [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://github.com/mmun))
- Refactor template init logic - 085e5e1 - Refactor template init logic - 085e5e1
@@ -745,7 +642,6 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3)
## v2.0.0-alpha.2 - March 6th, 2014 ## v2.0.0-alpha.2 - March 6th, 2014
- [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://github.com/jenseng)) - [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://github.com/jenseng))
- [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://github.com/jenseng)) - [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://github.com/jenseng))
- [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://github.com/waynedpj)) - [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://github.com/waynedpj))
@@ -755,13 +651,11 @@ Compatibility notes:
- Temporarily kill the AWS publish CI step - 8347ee2 - Temporarily kill the AWS publish CI step - 8347ee2
Compatibility notes: Compatibility notes:
- A JSON polyfill is required to run the compiler under IE8 and below. It's recommended that the precompiler be used in lieu of running the compiler on these legacy environments. - A JSON polyfill is required to run the compiler under IE8 and below. It's recommended that the precompiler be used in lieu of running the compiler on these legacy environments.
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2) [Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2)
## v2.0.0-alpha.1 - February 10th, 2014 ## v2.0.0-alpha.1 - February 10th, 2014
- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://github.com/kpdecker)) - [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://github.com/kpdecker))
- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://github.com/kpdecker)) - [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://github.com/kpdecker))
- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://github.com/kayleg)) - [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://github.com/kayleg))
@@ -784,7 +678,6 @@ Compatibility notes:
- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://github.com/blakeembrey)) - [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://github.com/blakeembrey))
Compatibility notes: Compatibility notes:
- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`. - `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`.
- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output. - Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output.
- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source. - `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source.
@@ -797,7 +690,6 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1) [Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
## v1.3.0 - January 1st, 2014 ## v1.3.0 - January 1st, 2014
- [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://github.com/machty)) - [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://github.com/machty))
- [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://github.com/nateirwin)) - [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://github.com/nateirwin))
- [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://github.com/fivetanley)) - [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://github.com/fivetanley))
@@ -812,14 +704,12 @@ Compatibility notes:
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.1...v1.3.0) [Commits](https://github.com/wycats/handlebars.js/compare/v1.2.1...v1.3.0)
## v1.2.1 - December 26th, 2013 ## v1.2.1 - December 26th, 2013
- [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://github.com/blakeembrey)) - [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://github.com/blakeembrey))
- [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://github.com/kpdecker)) - [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://github.com/kpdecker))
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...v1.2.1) [Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...v1.2.1)
## v1.2.0 - December 23rd, 2013 ## v1.2.0 - December 23rd, 2013
- [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://github.com/erwinw)) - [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://github.com/erwinw))
- [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://github.com/hamzaCM)) - [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://github.com/hamzaCM))
- [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://github.com/niki4810)) - [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://github.com/niki4810))
@@ -839,7 +729,6 @@ Compatibility notes:
- Fix handling of boolean escape in MustacheNode - b4968bb - Fix handling of boolean escape in MustacheNode - b4968bb
Compatibility notes: Compatibility notes:
- `@index` and `@first` are now supported for `each` iteration on objects - `@index` and `@first` are now supported for `each` iteration on objects
- `Handlebars.VM.checkRevision` and `Handlebars.JavaScriptCompiler.prototype.compilerInfo` now available to modify the version checking behavior. - `Handlebars.VM.checkRevision` and `Handlebars.JavaScriptCompiler.prototype.compilerInfo` now available to modify the version checking behavior.
- Browserify users may link to the runtime library via `require('handlebars/runtime')` - Browserify users may link to the runtime library via `require('handlebars/runtime')`
@@ -881,8 +770,8 @@ Compatibility notes:
- [#571](https://github.com/wycats/handlebars.js/issues/571) - uglify upgrade breaks compatibility with older versions of node - [#571](https://github.com/wycats/handlebars.js/issues/571) - uglify upgrade breaks compatibility with older versions of node
- [#587](https://github.com/wycats/handlebars.js/issues/587) - Partial inside partial breaks? - [#587](https://github.com/wycats/handlebars.js/issues/587) - Partial inside partial breaks?
Compatibility notes:
Compatibility notes:
- The project now includes separate artifacts for AMD, CommonJS, and global objects. - The project now includes separate artifacts for AMD, CommonJS, and global objects.
- AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims. - AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims.
- CommonJS/Node: Node loading occurs as normal via `require` - CommonJS/Node: Node loading occurs as normal via `require`
@@ -890,6 +779,7 @@ Compatibility notes:
- Build artifacts have been removed from the repository. [npm][npm], [components/handlebars.js][components], [cdnjs][cdnjs], or the [builds page][builds-page] should now be used as the source of built artifacts. - Build artifacts have been removed from the repository. [npm][npm], [components/handlebars.js][components], [cdnjs][cdnjs], or the [builds page][builds-page] should now be used as the source of built artifacts.
- Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument. - Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument.
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...v1.1.0) [Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...v1.1.0)
## v1.0.12 / 1.0.0 - May 31 2013 ## v1.0.12 / 1.0.0 - May 31 2013
@@ -907,7 +797,6 @@ Compatibility notes:
- Docs updates - Docs updates
Compatibility notes: Compatibility notes:
- The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not - The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not
follow this convention should add the additional brace value. follow this convention should add the additional brace value.
- Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available. - Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available.
@@ -974,13 +863,13 @@ changed.
Instead of: Instead of:
```js ```js
template(context, helpers, partials, [data]); template(context, helpers, partials, [data])
``` ```
Use: Use:
```js ```js
template(context, { helpers: helpers, partials: partials, data: data }); template(context, {helpers: helpers, partials: partials, data: data})
``` ```
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html [builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html
+1 -2
View File
@@ -1,8 +1,7 @@
{ {
"extends": [ "extends": [
"../.eslintrc.js", "../.eslintrc.js",
"plugin:es5/no-es2015", "plugin:es5/no-es2015"
"prettier"
], ],
"plugins": [ "plugins": [
"es5" "es5"
+1 -2
View File
@@ -26,8 +26,7 @@
window.expect = chai.expect; window.expect = chai.expect;
mocha.setup('bdd'); mocha.setup('bdd');
</script> </script>
<script src="/spec/vendor/json2.js"></script> <script src="/spec/env/require.js"></script>
<script src="/spec/vendor/require.js"></script>
<script src="/spec/env/common.js"></script> <script src="/spec/env/common.js"></script>
<script> <script>
+1 -3
View File
@@ -27,9 +27,7 @@
window.expect = chai.expect; window.expect = chai.expect;
mocha.setup('bdd'); mocha.setup('bdd');
</script> </script>
<script src="/spec/vendor/json2.js"></script> <script src="/spec/env/require.js"></script>
<script src="/spec/vendor/require.js"></script>
<script src="/spec/env/common.js"></script> <script src="/spec/env/common.js"></script>
<script> <script>
var requireFailure; var requireFailure;
+35 -115
View File
@@ -7,69 +7,23 @@ describe('ast', function() {
describe('BlockStatement', function() { describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() { it('should throw on mustache mismatch', function() {
shouldThrow( shouldThrow(function() {
function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}'); handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
}, }, Handlebars.Exception, "foo doesn't match bar - 2:5");
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
}); });
}); });
describe('helpers', function() { describe('helpers', function() {
describe('#helperExpression', function() { describe('#helperExpression', function() {
it('should handle mustache statements', function() { it('should handle mustache statements', function() {
equals( equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
AST.helpers.helperExpression({ equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
type: 'MustacheStatement', equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: {}
}),
true
);
}); });
it('should handle block statements', function() { it('should handle block statements', function() {
equals( equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
AST.helpers.helperExpression({ equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
type: 'BlockStatement', equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: {}
}),
true
);
}); });
it('should handle subexpressions', function() { it('should handle subexpressions', function() {
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true); equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
@@ -77,28 +31,16 @@ describe('ast', function() {
it('should work with non-helper nodes', function() { it('should work with non-helper nodes', function() {
equals(AST.helpers.helperExpression({type: 'Program'}), false); equals(AST.helpers.helperExpression({type: 'Program'}), false);
equals( equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
AST.helpers.helperExpression({ type: 'PartialStatement' }), equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
false equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
);
equals(
AST.helpers.helperExpression({ type: 'ContentStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'CommentStatement' }),
false
);
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false); equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false); equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false); equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false); equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
equals( equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
false
);
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false); equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
equals(AST.helpers.helperExpression({type: 'Hash'}), false); equals(AST.helpers.helperExpression({type: 'Hash'}), false);
@@ -119,18 +61,17 @@ describe('ast', function() {
/* eslint-disable no-multi-spaces */ /* eslint-disable no-multi-spaces */
ast = Handlebars.parse( ast = Handlebars.parse(
'line 1 {{line1Token}}\n' + // 1 'line 1 {{line1Token}}\n' // 1
' line 2 {{line2token}}\n' + // 2 + ' line 2 {{line2token}}\n' // 2
' line 3 {{#blockHelperOnLine3}}\n' + // 3 + ' line 3 {{#blockHelperOnLine3}}\n' // 3
'line 4{{line4token}}\n' + // 4 + 'line 4{{line4token}}\n' // 4
'line5{{else}}\n' + // 5 + 'line5{{else}}\n' // 5
'{{line6Token}}\n' + // 6 + '{{line6Token}}\n' // 6
'{{/blockHelperOnLine3}}\n' + // 7 + '{{/blockHelperOnLine3}}\n' // 7
'{{#open}}\n' + // 8 + '{{#open}}\n' // 8
'{{else inverse}}\n' + // 9 + '{{else inverse}}\n' // 9
'{{else}}\n' + // 10 + '{{else}}\n' // 10
'{{/open}}' + '{{/open}}'); // 11
); // 11
/* eslint-enable no-multi-spaces */ /* eslint-enable no-multi-spaces */
body = ast.body; body = ast.body;
@@ -208,9 +149,7 @@ describe('ast', function() {
}); });
it('block statements', function() { it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing(' {{# comment~}} \nfoo\n {{~/comment}}');
' {{# comment~}} \nfoo\n {{~/comment}}'
);
equals(ast.body[0].value, ' '); equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n '); equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
@@ -228,9 +167,7 @@ describe('ast', function() {
}); });
describe('blocks - parseWithoutProcessing', function() { describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() { it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].value, ' '); equals(ast.body[0].value, ' ');
@@ -241,17 +178,13 @@ describe('ast', function() {
equals(ast.body[2].value, ' '); equals(ast.body[2].value, ' ');
}); });
it('initial block mustaches', function() { it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing('{{# comment}} \nfoo\n {{/comment}}'),
'{{# comment}} \nfoo\n {{/comment}}'
),
block = ast.body[0]; block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n '); equals(block.program.body[0].value, ' \nfoo\n ');
}); });
it('mustaches with children', function() { it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing('{{# comment}} \n{{foo}}\n {{/comment}}'),
'{{# comment}} \n{{foo}}\n {{/comment}}'
),
block = ast.body[0]; block = ast.body[0];
equals(block.program.body[0].value, ' \n'); equals(block.program.body[0].value, ' \n');
@@ -259,9 +192,7 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n '); equals(block.program.body[2].value, '\n ');
}); });
it('nested block mustaches', function() { it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -271,9 +202,7 @@ describe('ast', function() {
equals(block.inverse.body[0].value, ' \n bar \n '); equals(block.inverse.body[0].value, ' \n bar \n ');
}); });
it('column 0 block mustaches', function() { it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing( var ast = Handlebars.parseWithoutProcessing('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].omit, undefined); equals(ast.body[0].omit, undefined);
@@ -286,9 +215,7 @@ describe('ast', function() {
}); });
describe('blocks', function() { describe('blocks', function() {
it('marks block mustaches as standalone', function() { it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse( var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].value, ''); equals(ast.body[0].value, '');
@@ -313,9 +240,7 @@ describe('ast', function() {
equals(block.program.body[2].value, '\n'); equals(block.program.body[2].value, '\n');
}); });
it('marks nested block mustaches as standalone', function() { it('marks nested block mustaches as standalone', function() {
var ast = Handlebars.parse( var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -327,9 +252,7 @@ describe('ast', function() {
equals(body[0].value, ''); equals(body[0].value, '');
}); });
it('does not mark nested block mustaches as standalone', function() { it('does not mark nested block mustaches as standalone', function() {
var ast = Handlebars.parse( var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[1]; block = body[1];
@@ -341,9 +264,7 @@ describe('ast', function() {
equals(body[0].omit, undefined); equals(body[0].omit, undefined);
}); });
it('does not mark nested initial block mustaches as standalone', function() { it('does not mark nested initial block mustaches as standalone', function() {
var ast = Handlebars.parse( var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
),
body = ast.body[0].program.body, body = ast.body[0].program.body,
block = body[0]; block = body[0];
@@ -354,9 +275,7 @@ describe('ast', function() {
}); });
it('marks column 0 block mustaches as standalone', function() { it('marks column 0 block mustaches as standalone', function() {
var ast = Handlebars.parse( var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1]; block = ast.body[1];
equals(ast.body[0].omit, undefined); equals(ast.body[0].omit, undefined);
@@ -429,3 +348,4 @@ describe('ast', function() {
}); });
}); });
}); });
+115 -418
View File
@@ -18,12 +18,8 @@ describe('basic context', function() {
}); });
it('compiling with a basic context', function() { it('compiling with a basic context', function() {
shouldCompileTo( shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
'Goodbye\n{{cruel}}\n{{world}}!', 'It works if all the required keys are provided');
{ cruel: 'cruel', world: 'world' },
'Goodbye\ncruel\nworld!',
'It works if all the required keys are provided'
);
}); });
it('compiling with a string context', function() { it('compiling with a string context', function() {
@@ -31,26 +27,15 @@ describe('basic context', function() {
}); });
it('compiling with an undefined context', function() { it('compiling with an undefined context', function() {
shouldCompileTo( shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
'Goodbye\n{{cruel}}\n{{world.bar}}!',
undefined,
'Goodbye\n\n!'
);
shouldCompileTo( shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
'{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}',
undefined,
'Goodbye'
);
}); });
it('comments', function() { it('comments', function() {
shouldCompileTo( shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
'{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
{ cruel: 'cruel', world: 'world' }, 'comments are ignored');
'Goodbye\ncruel\nworld!',
'comments are ignored'
);
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah'); shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah'); shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
@@ -62,91 +47,52 @@ describe('basic context', function() {
it('boolean', function() { it('boolean', function() {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
shouldCompileTo( shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
string, 'booleans show the contents when true');
{ goodbye: true, world: 'world' },
'GOODBYE cruel world!',
'booleans show the contents when true'
);
shouldCompileTo( shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
string, 'booleans do not show the contents when false');
{ goodbye: false, world: 'world' },
'cruel world!',
'booleans do not show the contents when false'
);
}); });
it('zeros', function() { it('zeros', function() {
shouldCompileTo( shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
'num1: {{num1}}, num2: {{num2}}', 'num1: 42, num2: 0');
{ num1: 42, num2: 0 },
'num1: 42, num2: 0'
);
shouldCompileTo('num: {{.}}', 0, 'num: 0'); shouldCompileTo('num: {{.}}', 0, 'num: 0');
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0'); shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
}); });
it('false', function() { it('false', function() {
/* eslint-disable no-new-wrappers */ /* eslint-disable no-new-wrappers */
shouldCompileTo( shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
'val1: {{val1}}, val2: {{val2}}',
{ val1: false, val2: new Boolean(false) },
'val1: false, val2: false'
);
shouldCompileTo('val: {{.}}', false, 'val: false'); shouldCompileTo('val: {{.}}', false, 'val: false');
shouldCompileTo( shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
'val: {{val1/val2}}',
{ val1: { val2: false } },
'val: false'
);
shouldCompileTo( shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
'val1: {{{val1}}}, val2: {{{val2}}}', shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
{ val1: false, val2: new Boolean(false) },
'val1: false, val2: false'
);
shouldCompileTo(
'val: {{{val1/val2}}}',
{ val1: { val2: false } },
'val: false'
);
/* eslint-enable */ /* eslint-enable */
}); });
it('should handle undefined and null', function() { it('should handle undefined and null', function() {
shouldCompileTo( shouldCompileTo('{{awesome undefined null}}',
'{{awesome undefined null}}',
{ {
awesome: function(_undefined, _null, options) { awesome: function(_undefined, _null, options) {
return ( return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
(_undefined === undefined) +
' ' +
(_null === null) +
' ' +
typeof options
);
} }
}, },
'true true object' 'true true object');
); shouldCompileTo('{{undefined}}',
shouldCompileTo(
'{{undefined}}',
{ {
undefined: function() { 'undefined': function() {
return 'undefined!'; return 'undefined!';
} }
}, },
'undefined!' 'undefined!');
); shouldCompileTo('{{null}}',
shouldCompileTo(
'{{null}}',
{ {
null: function() { 'null': function() {
return 'null!'; return 'null!';
} }
}, },
'null!' 'null!');
);
}); });
it('newlines', function() { it('newlines', function() {
@@ -155,300 +101,118 @@ describe('basic context', function() {
}); });
it('escaping text', function() { it('escaping text', function() {
shouldCompileTo( shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
"Awesome's", 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");
"Awesome's", shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
"text is escaped so that it doesn't get caught on single quotes" shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
);
shouldCompileTo(
'Awesome\\',
{},
'Awesome\\',
"text is escaped so that the closing quote can't be ignored"
);
shouldCompileTo(
'Awesome\\\\ foo',
{},
'Awesome\\\\ foo',
"text is escaped so that it doesn't mess up backslashes"
);
shouldCompileTo(
'Awesome {{foo}}',
{ foo: '\\' },
'Awesome \\',
"text is escaped so that it doesn't mess up backslashes"
);
shouldCompileTo(
" ' ' ",
{},
" ' ' ",
'double quotes never produce invalid javascript'
);
}); });
it('escaping expressions', function() { it('escaping expressions', function() {
shouldCompileTo( shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
'{{{awesome}}}', "expressions with 3 handlebars aren't escaped");
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with 3 handlebars aren't escaped"
);
shouldCompileTo( shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
'{{&awesome}}', "expressions with {{& handlebars aren't escaped");
{ awesome: "&'\\<>" },
"&'\\<>",
"expressions with {{& handlebars aren't escaped"
);
shouldCompileTo( shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'{{awesome}}', 'by default expressions should be escaped');
{ awesome: '&"\'`\\<>' },
'&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
'by default expressions should be escaped'
);
shouldCompileTo( shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: &lt;b&gt;'}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'{{awesome}}', 'escaping should properly handle amperstands');
{ awesome: 'Escaped, <b> looks like: &lt;b&gt;' },
'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
'escaping should properly handle amperstands'
);
}); });
it("functions returning safestrings shouldn't be escaped", function() { it("functions returning safestrings shouldn't be escaped", function() {
var hash = { var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
awesome: function() { shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
return new Handlebars.SafeString("&'\\<>"); "functions returning safestrings aren't escaped");
}
};
shouldCompileTo(
'{{awesome}}',
hash,
"&'\\<>",
"functions returning safestrings aren't escaped"
);
}); });
it('functions', function() { it('functions', function() {
shouldCompileTo( shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
'{{awesome}}', 'functions are called and render their output');
{ shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
awesome: function() { 'functions are bound to the context');
return 'Awesome';
}
},
'Awesome',
'functions are called and render their output'
);
shouldCompileTo(
'{{awesome}}',
{
awesome: function() {
return this.more;
},
more: 'More awesome'
},
'More awesome',
'functions are bound to the context'
);
}); });
it('functions with context argument', function() { it('functions with context argument', function() {
shouldCompileTo( shouldCompileTo('{{awesome frank}}',
'{{awesome frank}}', {awesome: function(context) { return context; },
{ frank: 'Frank'},
awesome: function(context) { 'Frank', 'functions are called with context arguments');
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('pathed functions with context argument', function() { it('pathed functions with context argument', function() {
shouldCompileTo( shouldCompileTo('{{bar.awesome frank}}',
'{{bar.awesome frank}}', {bar: {awesome: function(context) { return context; }},
{ frank: 'Frank'},
bar: { 'Frank', 'functions are called with context arguments');
awesome: function(context) {
return context;
}
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('depthed functions with context argument', function() { it('depthed functions with context argument', function() {
shouldCompileTo( shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
'{{#with frank}}{{../awesome .}}{{/with}}', {awesome: function(context) { return context; },
{ frank: 'Frank'},
awesome: function(context) { 'Frank', 'functions are called with context arguments');
return context;
},
frank: 'Frank'
},
'Frank',
'functions are called with context arguments'
);
}); });
it('block functions with context argument', function() { it('block functions with context argument', function() {
shouldCompileTo( shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
'{{#awesome 1}}inner {{.}}{{/awesome}}', {awesome: function(context, options) { return options.fn(context); }},
{ 'inner 1', 'block functions are called with context and options');
awesome: function(context, options) {
return options.fn(context);
}
},
'inner 1',
'block functions are called with context and options'
);
}); });
it('depthed block functions with context argument', function() { it('depthed block functions with context argument', function() {
shouldCompileTo( shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
'{{#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');
value: true,
awesome: function(context, options) {
return options.fn(context);
}
},
'inner 1',
'block functions are called with context and options'
);
}); });
it('block functions without context argument', function() { it('block functions without context argument', function() {
shouldCompileTo( shouldCompileTo('{{#awesome}}inner{{/awesome}}',
'{{#awesome}}inner{{/awesome}}', {awesome: function(options) { return options.fn(this); }},
{ 'inner', 'block functions are called with options');
awesome: function(options) {
return options.fn(this);
}
},
'inner',
'block functions are called with options'
);
}); });
it('pathed block functions without context argument', function() { it('pathed block functions without context argument', function() {
shouldCompileTo( shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
'{{#foo.awesome}}inner{{/foo.awesome}}', {foo: {awesome: function() { return this; }}},
{ 'inner', 'block functions are called with options');
foo: {
awesome: function() {
return this;
}
}
},
'inner',
'block functions are called with options'
);
}); });
it('depthed block functions without context argument', function() { it('depthed block functions without context argument', function() {
shouldCompileTo( shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}', {value: true, awesome: function() { return this; }},
{ 'inner', 'block functions are called with options');
value: true,
awesome: function() {
return this;
}
},
'inner',
'block functions are called with options'
);
}); });
it('paths with hyphens', function() { it('paths with hyphens', function() {
shouldCompileTo( shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
'{{foo-bar}}', shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
{ 'foo-bar': 'baz' }, shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
'baz',
'Paths can contain hyphens (-)'
);
shouldCompileTo(
'{{foo.foo-bar}}',
{ foo: { 'foo-bar': 'baz' } },
'baz',
'Paths can contain hyphens (-)'
);
shouldCompileTo(
'{{foo/foo-bar}}',
{ foo: { 'foo-bar': 'baz' } },
'baz',
'Paths can contain hyphens (-)'
);
}); });
it('nested paths', function() { it('nested paths', function() {
shouldCompileTo( shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
'Goodbye {{alan/expression}} world!', 'Goodbye beautiful world!', 'Nested paths access nested objects');
{ alan: { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Nested paths access nested objects'
);
}); });
it('nested paths with empty string value', function() { it('nested paths with empty string value', function() {
shouldCompileTo( shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
'Goodbye {{alan/expression}} world!', 'Goodbye world!', 'Nested paths access nested objects with empty string');
{ alan: { expression: '' } },
'Goodbye world!',
'Nested paths access nested objects with empty string'
);
}); });
it('literal paths', function() { it('literal paths', function() {
shouldCompileTo( shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
'Goodbye {{[@alan]/expression}} world!', 'Goodbye beautiful world!', 'Literal paths can be used');
{ '@alan': { expression: 'beautiful' } }, shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
'Goodbye beautiful world!', 'Goodbye beautiful world!', 'Literal paths can be used');
'Literal paths can be used'
);
shouldCompileTo(
'Goodbye {{[foo bar]/expression}} world!',
{ 'foo bar': { expression: 'beautiful' } },
'Goodbye beautiful world!',
'Literal paths can be used'
);
}); });
it('literal references', function() { it('literal references', function() {
shouldCompileTo( shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
'Goodbye {{[foo bar]}} world!', shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
{ 'foo bar': 'beautiful' }, shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
'Goodbye beautiful world!' shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!');
); shouldCompileTo('Goodbye {{"foo\'bar"}} world!', {"foo'bar": 'beautiful'}, 'Goodbye beautiful world!');
shouldCompileTo( shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
'Goodbye {{"foo bar"}} world!',
{ 'foo bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
"Goodbye {{'foo bar'}} world!",
{ 'foo bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
'Goodbye {{"foo[bar"}} world!',
{ 'foo[bar': 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
'Goodbye {{"foo\'bar"}} world!',
{ "foo'bar": 'beautiful' },
'Goodbye beautiful world!'
);
shouldCompileTo(
"Goodbye {{'foo\"bar'}} world!",
{ 'foo"bar': 'beautiful' },
'Goodbye beautiful world!'
);
}); });
it("that current context path ({{.}}) doesn't hit helpers", function() { it("that current context path ({{.}}) doesn't hit helpers", function() {
@@ -463,105 +227,57 @@ describe('basic context', function() {
it('this keyword in paths', function() { it('this keyword in paths', function() {
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}'; var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']}; var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo( shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
string, 'This keyword in paths evaluates to current context');
hash,
'goodbyeGoodbyeGOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{this/text}}{{/hellos}}'; string = '{{#hellos}}{{this/text}}{{/hellos}}';
hash = { hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
};
shouldCompileTo(
string,
hash,
'helloHelloHELLO',
'This keyword evaluates in more complex paths'
);
}); });
it('this keyword nested inside path', function() { it('this keyword nested inside path', function() {
shouldThrow( shouldThrow(function() {
function() {
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}'); CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
}, }, Error, 'Invalid path: text/this - 1:13');
Error,
'Invalid path: text/this - 1:13'
);
shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar'); shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar'); shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
}); });
it('this keyword in helpers', function() { it('this keyword in helpers', function() {
var helpers = { var helpers = {foo: function(value) {
foo: function(value) {
return 'bar ' + value; return 'bar ' + value;
} }};
};
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}'; var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']}; var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
shouldCompileTo( shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
string, 'This keyword in paths evaluates to current context');
[hash, helpers],
'bar goodbyebar Goodbyebar GOODBYE',
'This keyword in paths evaluates to current context'
);
string = '{{#hellos}}{{foo this/text}}{{/hellos}}'; string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
hash = { hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
};
shouldCompileTo(
string,
[hash, helpers],
'bar hellobar Hellobar HELLO',
'This keyword evaluates in more complex paths'
);
}); });
it('this keyword nested inside helpers param', function() { it('this keyword nested inside helpers param', function() {
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
shouldThrow( shouldThrow(function() {
function() {
CompilerContext.compile(string); CompilerContext.compile(string);
}, }, Error, 'Invalid path: text/this - 1:17');
Error,
'Invalid path: text/this - 1:17'
);
shouldCompileTo( shouldCompileTo(
'{{foo [this]}}', '{{foo [this]}}',
{ {foo: function(value) { return value; }, 'this': 'bar'},
foo: function(value) { 'bar');
return value;
},
this: 'bar'
},
'bar'
);
shouldCompileTo( shouldCompileTo(
'{{foo text/[this]}}', '{{foo text/[this]}}',
{ {foo: function(value) { return value; }, text: {'this': 'bar'}},
foo: function(value) { 'bar');
return value;
},
text: { this: 'bar' }
},
'bar'
);
}); });
it('pass string literals', function() { it('pass string literals', function() {
shouldCompileTo('{{"foo"}}', {}, ''); shouldCompileTo('{{"foo"}}', {}, '');
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar'); shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
shouldCompileTo( shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
'{{#"foo"}}{{.}}{{/"foo"}}',
{ foo: ['bar', 'baz'] },
'barbaz'
);
}); });
it('pass number literals', function() { it('pass number literals', function() {
@@ -569,21 +285,13 @@ describe('basic context', function() {
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar'); shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
shouldCompileTo('{{12.34}}', {}, ''); shouldCompileTo('{{12.34}}', {}, '');
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar'); shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
shouldCompileTo( shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
'{{12.34 1}}',
{
'12.34': function(arg) {
return 'bar' + arg;
}
},
'bar1'
);
}); });
it('pass boolean literals', function() { it('pass boolean literals', function() {
shouldCompileTo('{{true}}', {}, ''); shouldCompileTo('{{true}}', {}, '');
shouldCompileTo('{{true}}', { '': 'foo' }, ''); shouldCompileTo('{{true}}', { '': 'foo' }, '');
shouldCompileTo('{{false}}', { false: 'foo' }, 'foo'); shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
}); });
it('should handle literals in subexpression', function() { it('should handle literals in subexpression', function() {
@@ -592,17 +300,6 @@ describe('basic context', function() {
return arg; return arg;
} }
}; };
shouldCompileTo( shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
'{{foo (false)}}',
[
{
false: function() {
return 'bar';
}
},
helpers
],
'bar'
);
}); });
}); });
+80 -238
View File
@@ -1,99 +1,51 @@
describe('blocks', function() { describe('blocks', function() {
it('array', function() { it('array', function() {
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
var hash = { var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
world: 'world' 'Arrays iterate over the contents when not empty');
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'Arrays iterate over the contents when not empty'
);
shouldCompileTo( shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
string, 'Arrays ignore the contents when empty');
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
}); });
it('array without data', function() { it('array without data', function() {
var string = var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = { shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , false],
'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
);
}); });
it('array with @index', function() { it('array with @index', function() {
var string = var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
}); });
it('empty block', function() { it('empty block', function() {
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
var hash = { var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], shouldCompileTo(string, hash, 'cruel world!',
world: 'world' 'Arrays iterate over the contents when not empty');
};
shouldCompileTo(
string,
hash,
'cruel world!',
'Arrays iterate over the contents when not empty'
);
shouldCompileTo( shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
string, 'Arrays ignore the contents when empty');
{ goodbyes: [], world: 'world' },
'cruel world!',
'Arrays ignore the contents when empty'
);
}); });
it('block with complex lookup', function() { it('block with complex lookup', function() {
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}'; var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
var hash = { var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
name: 'Alan',
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
};
shouldCompileTo( shouldCompileTo(string, hash, 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
string, 'Templates can access variables in contexts up the stack with relative path syntax');
hash,
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
'Templates can access variables in contexts up the stack with relative path syntax'
);
}); });
it('multiple blocks with complex lookup', function() { it('multiple blocks with complex lookup', function() {
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}'; var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
var hash = { var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
name: 'Alan',
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
};
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan'); shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
}); });
@@ -107,202 +59,111 @@ describe('blocks', function() {
}); });
it('block with deep nested complex lookup', function() { it('block with deep nested complex lookup', function() {
var string = var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'; var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] };
var hash = {
omg: 'OMG!',
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
};
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!'); shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
}); });
it('works with cached blocks', function() { it('works with cached blocks', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
{ data: false }
);
var result = template({ var result = template({person: [{first: 'Alan', last: 'Johnson'}, {first: 'Alan', last: 'Johnson'}]});
person: [
{ first: 'Alan', last: 'Johnson' },
{ first: 'Alan', last: 'Johnson' }
]
});
equals(result, 'Alan JohnsonAlan Johnson'); equals(result, 'Alan JohnsonAlan Johnson');
}); });
describe('inverted sections', function() { describe('inverted sections', function() {
it('inverted sections with unset value', function() { it('inverted sections with unset value', function() {
var string = var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {}; var hash = {};
shouldCompileTo( shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
string,
hash,
'Right On!',
"Inverted section rendered when value isn't set."
);
}); });
it('inverted section with false value', function() { it('inverted section with false value', function() {
var string = var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {goodbyes: false}; var hash = {goodbyes: false};
shouldCompileTo( shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
string,
hash,
'Right On!',
'Inverted section rendered when value is false.'
);
}); });
it('inverted section with empty set', function() { it('inverted section with empty set', function() {
var string = var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
var hash = {goodbyes: []}; var hash = {goodbyes: []};
shouldCompileTo( shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
string,
hash,
'Right On!',
'Inverted section rendered when value is empty set.'
);
}); });
it('block inverted sections', function() { it('block inverted sections', function() {
shouldCompileTo( shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
'{{#people}}{{name}}{{^}}{{none}}{{/people}}', 'No people');
{ none: 'No people' },
'No people'
);
}); });
it('chained inverted sections', function() { it('chained inverted sections', function() {
shouldCompileTo( shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'},
'{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', 'No people');
{ none: 'No people' }, shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'},
'No people' 'No people');
); shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', {none: 'No people'},
shouldCompileTo( 'No people');
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
{ none: 'No people' },
'No people'
);
shouldCompileTo(
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
{ none: 'No people' },
'No people'
);
}); });
it('chained inverted sections with mismatch', function() { it('chained inverted sections with mismatch', function() {
shouldThrow(function() { shouldThrow(function() {
shouldCompileTo( shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', 'No people');
{ none: 'No people' },
'No people'
);
}, Error); }, Error);
}); });
it('block inverted sections with empty arrays', function() { it('block inverted sections with empty arrays', function() {
shouldCompileTo( shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
'{{#people}}{{name}}{{^}}{{none}}{{/people}}', 'No people');
{ none: 'No people', people: [] },
'No people'
);
}); });
}); });
describe('standalone sections', function() { describe('standalone sections', function() {
it('block standalone else sections', function() { it('block standalone else sections', function() {
shouldCompileTo( shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', 'No people\n');
{ none: 'No people' }, shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
'No people\n' 'No people\n');
); shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
shouldCompileTo( 'No people\n');
'{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
{ none: 'No people' },
'No people\n'
);
shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
}); });
it('block standalone else sections can be disabled', function() { it('block standalone else sections can be disabled', function() {
shouldCompileTo( shouldCompileTo(
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}], [{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
'\nNo people\n\n' '\nNo people\n\n');
);
shouldCompileTo( shouldCompileTo(
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n', '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}], [{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
'\nNo people\n\n' '\nNo people\n\n');
);
}); });
it('block standalone chained else sections', function() { it('block standalone chained else sections', function() {
shouldCompileTo( shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', 'No people\n');
{ none: 'No people' }, shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
'No people\n' 'No people\n');
);
shouldCompileTo(
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
{ none: 'No people' },
'No people\n'
);
}); });
it('should handle nesting', function() { it('should handle nesting', function() {
shouldCompileTo( shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
'{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
{ data: [1, 3, 5] },
'1\n3\n5\nOK.'
);
}); });
}); });
describe('compat mode', function() { describe('compat mode', function() {
it('block with deep recursive lookup lookup', function() { it('block with deep recursive lookup lookup', function() {
var string = var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }; var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
shouldCompileTo( shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
string,
[hash, undefined, undefined, true],
'Goodbye cruel OMG!'
);
}); });
it('block with deep recursive pathed lookup', function() { it('block with deep recursive pathed lookup', function() {
var string = var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
var hash = {
omg: { yes: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
};
shouldCompileTo( shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
string,
[hash, undefined, undefined, true],
'Goodbye cruel OMG!'
);
}); });
it('block with missed recursive lookup', function() { it('block with missed recursive lookup', function() {
var string = var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'; var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
var hash = {
omg: { no: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
};
shouldCompileTo( shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
string,
[hash, undefined, undefined, true],
'Goodbye cruel '
);
}); });
}); });
@@ -322,8 +183,7 @@ describe('blocks', function() {
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator}}{{/helper}}', '{{#helper}}{{*decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, {hash: {}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should apply allow undefined return', function() { it('should apply allow undefined return', function() {
var helpers = { var helpers = {
@@ -339,8 +199,7 @@ describe('blocks', function() {
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator}}suc{{/helper}}', '{{#helper}}{{*decorator}}suc{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, {hash: {}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should apply block decorators', function() { it('should apply block decorators', function() {
@@ -358,8 +217,7 @@ describe('blocks', function() {
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, {hash: {}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should support nested decorators', function() { it('should support nested decorators', function() {
var helpers = { var helpers = {
@@ -379,8 +237,7 @@ describe('blocks', function() {
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, {hash: {}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should apply multiple decorators', function() { it('should apply multiple decorators', function() {
@@ -398,8 +255,7 @@ describe('blocks', function() {
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}', '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
{hash: {}, helpers: helpers, decorators: decorators}, {hash: {}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should access parent variables', function() { it('should access parent variables', function() {
@@ -416,9 +272,8 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#helper}}{{*decorator foo}}{{/helper}}', '{{#helper}}{{*decorator foo}}{{/helper}}',
{ hash: { foo: 'success' }, helpers: helpers, decorators: decorators }, {hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
'success' 'success');
);
}); });
it('should work with root program', function() { it('should work with root program', function() {
var run; var run;
@@ -431,9 +286,8 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{*decorator "success"}}', '{{*decorator "success"}}',
{ hash: { foo: 'success' }, decorators: decorators }, {hash: {'foo': 'success'}, decorators: decorators},
'' '');
);
equals(run, true); equals(run, true);
}); });
it('should fail when accessing variables from root', function() { it('should fail when accessing variables from root', function() {
@@ -447,9 +301,8 @@ describe('blocks', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{*decorator foo}}', '{{*decorator foo}}',
{ hash: { foo: 'fail' }, decorators: decorators }, {hash: {'foo': 'fail'}, decorators: decorators},
'' '');
);
equals(run, true); equals(run, true);
}); });
@@ -482,23 +335,12 @@ describe('blocks', function() {
equals(handlebarsEnv.decorators.bar, undefined); equals(handlebarsEnv.decorators.bar, undefined);
}); });
it('fails with multiple and args', function() { it('fails with multiple and args', function() {
shouldThrow( shouldThrow(function() {
function() { handlebarsEnv.registerDecorator({
handlebarsEnv.registerDecorator( world: function() { return 'world!'; },
{ testHelper: function() { return 'found it!'; }
world: function() { }, {});
return 'world!'; }, Error, 'Arg not supported with multiple decorators');
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
}); });
}); });
}); });
+91 -371
View File
@@ -2,154 +2,62 @@ describe('builtin helpers', function() {
describe('#if', function() { describe('#if', function() {
it('if', function() { it('if', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo( shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
string, 'if with boolean argument shows the contents when true');
{ goodbye: true, world: 'world' }, shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
'GOODBYE cruel world!', 'if with string argument shows the contents');
'if with boolean argument shows the contents when true' shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
); 'if with boolean argument does not show the contents when false');
shouldCompileTo( shouldCompileTo(string, {world: 'world'}, 'cruel world!',
string, 'if with undefined does not show the contents');
{ goodbye: 'dummy', world: 'world' }, shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
'GOODBYE cruel world!', 'if with non-empty array shows the contents');
'if with string argument shows the contents' shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
); 'if with empty array does not show the contents');
shouldCompileTo( shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
string, 'if with zero does not show the contents');
{ goodbye: false, world: 'world' }, shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
'cruel world!', {goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
'if with boolean argument does not show the contents when false' 'if with zero does not show the contents');
);
shouldCompileTo(
string,
{ world: 'world' },
'cruel world!',
'if with undefined does not show the contents'
);
shouldCompileTo(
string,
{ goodbye: ['foo'], world: 'world' },
'GOODBYE cruel world!',
'if with non-empty array shows the contents'
);
shouldCompileTo(
string,
{ goodbye: [], world: 'world' },
'cruel world!',
'if with empty array does not show the contents'
);
shouldCompileTo(
string,
{ goodbye: 0, world: 'world' },
'cruel world!',
'if with zero does not show the contents'
);
shouldCompileTo(
'{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
{ goodbye: 0, world: 'world' },
'GOODBYE cruel world!',
'if with zero does not show the contents'
);
}); });
it('if with function argument', function() { it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
shouldCompileTo( shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
string, 'if with function shows the contents when function returns true');
{ shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
goodbye: function() { 'if with function shows the contents when function returns string');
return true; shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
}, 'if with function does not show the contents when returns false');
world: 'world' shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
}, 'if with function does not show the contents when returns undefined');
'GOODBYE cruel world!',
'if with function shows the contents when function returns true'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.world;
},
world: 'world'
},
'GOODBYE cruel world!',
'if with function shows the contents when function returns string'
);
shouldCompileTo(
string,
{
goodbye: function() {
return false;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns false'
);
shouldCompileTo(
string,
{
goodbye: function() {
return this.foo;
},
world: 'world'
},
'cruel world!',
'if with function does not show the contents when returns undefined'
);
}); });
it('should not change the depth list', function() { it('should not change the depth list', function() {
var string = var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
shouldCompileTo(
string,
{ foo: { goodbye: true }, world: 'world' },
'GOODBYE cruel world!'
);
}); });
}); });
describe('#with', function() { describe('#with', function() {
it('with', function() { it('with', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}'; var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo( shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
}); });
it('with with function argument', function() { it('with with function argument', function() {
var string = '{{#with person}}{{first}} {{last}}{{/with}}'; var string = '{{#with person}}{{first}} {{last}}{{/with}}';
shouldCompileTo( shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
string,
{
person: function() {
return { first: 'Alan', last: 'Johnson' };
}
},
'Alan Johnson'
);
}); });
it('with with else', function() { it('with with else', function() {
var string = var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
'{{#with person}}Person is present{{else}}Person is not present{{/with}}';
shouldCompileTo(string, {}, 'Person is not present'); shouldCompileTo(string, {}, 'Person is not present');
}); });
it('with provides block parameter', function() { it('with provides block parameter', function() {
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}'; var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
shouldCompileTo( shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
string,
{ person: { first: 'Alan', last: 'Johnson' } },
'Alan Johnson'
);
}); });
it('works when data is disabled', function() { it('works when data is disabled', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
'{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}',
{ data: false }
);
var result = template({person: {first: 'Alan', last: 'Johnson'}}); var result = template({person: {first: 'Alan', last: 'Johnson'}});
equals(result, 'Alan Johnson'); equals(result, 'Alan Johnson');
@@ -165,50 +73,20 @@ describe('builtin helpers', function() {
it('each', function() { it('each', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = { var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
goodbyes: [ shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
{ text: 'goodbye' }, 'each with array argument iterates over the contents when not empty');
{ text: 'Goodbye' }, shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
{ text: 'GOODBYE' } 'each with array argument ignores the contents when empty');
],
world: 'world'
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array argument iterates over the contents when not empty'
);
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'each with array argument ignores the contents when empty'
);
}); });
it('each without data', function() { it('each without data', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = { var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
goodbyes: [ shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
shouldCompileTo(
string,
[hash, , , , false],
'goodbye! Goodbye! GOODBYE! cruel world!'
);
hash = {goodbyes: 'cruel', world: 'world'}; hash = {goodbyes: 'cruel', world: 'world'};
shouldCompileTo( shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
'{{#each .}}{{.}}{{/each}}',
[hash, , , , false],
'cruelworld'
);
}); });
it('each without context', function() { it('each without context', function() {
@@ -217,8 +95,7 @@ describe('builtin helpers', function() {
}); });
it('each with an object and @key', function() { it('each with an object and @key', function() {
var string = var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
function Clazz() { function Clazz() {
this['<b>#1</b>'] = {text: 'goodbye'}; this['<b>#1</b>'] = {text: 'goodbye'};
@@ -231,113 +108,57 @@ describe('builtin helpers', function() {
// so we need to check both possible orders // so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash); var actual = compileWithPartials(string, hash);
var expected1 = var expected1 = '&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
'&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!'; var expected2 = '2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
var expected2 =
'2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
equals( equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
actual === expected1 || actual === expected2,
true,
'each with object argument iterates over the contents when not empty'
);
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!'); shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
}); });
it('each with @index', function() { it('each with @index', function() {
var string = var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
}); });
it('each with nested @index', function() { it('each with nested @index', function() {
var string = var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
'{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( 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');
result,
'0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!',
'The @index variable is used'
);
}); });
it('each with block params', function() { it('each with block params', function() {
var string = var string = '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'};
var hash = {
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
result,
'0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'
);
}); });
it('each object with @index', function() { it('each object with @index', function() {
var string = var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
var hash = {
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
c: { text: 'GOODBYE' }
},
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'The @index variable is used'
);
}); });
it('each with @first', function() { it('each with @first', function() {
var string = var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -346,34 +167,18 @@ describe('builtin helpers', function() {
}); });
it('each with nested @first', function() { it('each with nested @first', function() {
var string = var string = '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
'{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
result,
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!',
'The @first variable is used'
);
}); });
it('each object with @first', function() { it('each object with @first', function() {
var string = var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var hash = {
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -382,16 +187,8 @@ describe('builtin helpers', function() {
}); });
it('each with @last', function() { it('each with @last', function() {
var string = var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -400,12 +197,8 @@ describe('builtin helpers', function() {
}); });
it('each object with @last', function() { it('each object with @last', function() {
var string = var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'; var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
var hash = {
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
@@ -414,73 +207,32 @@ describe('builtin helpers', function() {
}); });
it('each with nested @last', function() { it('each with nested @last', function() {
var string = var string = '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
'{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'; var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
var hash = {
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
result,
'(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!',
'The @last variable is used'
);
}); });
it('each with function argument', function() { it('each with function argument', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var hash = { var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'};
goodbyes: function() { shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
return [ 'each with array function argument iterates over the contents when not empty');
{ text: 'goodbye' }, shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
{ text: 'Goodbye' }, 'each with array function argument ignores the contents when empty');
{ text: 'GOODBYE' }
];
},
world: 'world'
};
shouldCompileTo(
string,
hash,
'goodbye! Goodbye! GOODBYE! cruel world!',
'each with array function argument iterates over the contents when not empty'
);
shouldCompileTo(
string,
{ goodbyes: [], world: 'world' },
'cruel world!',
'each with array function argument ignores the contents when empty'
);
}); });
it('each object when last key is an empty string', function() { it('each object when last key is an empty string', function() {
var string = var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'};
var hash = {
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
'': { text: 'GOODBYE' }
},
world: 'world'
};
var template = CompilerContext.compile(string); var template = CompilerContext.compile(string);
var result = template(hash); var result = template(hash);
equal( equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'Empty string key is not skipped');
result,
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
'Empty string key is not skipped'
);
}); });
it('data passed to helpers', function() { it('data passed to helpers', function() {
@@ -497,16 +249,10 @@ describe('builtin helpers', function() {
}); });
it('each on implicit context', function() { it('each on implicit context', function() {
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
var template = CompilerContext.compile(
'{{#each}}{{text}}! {{/each}}cruel world!'
);
template({}); template({});
}, }, handlebarsEnv.Exception, 'Must pass iterator to #each');
handlebarsEnv.Exception,
'Must pass iterator to #each'
);
}); });
if (global.Symbol && global.Symbol.iterator) { if (global.Symbol && global.Symbol.iterator) {
@@ -530,25 +276,13 @@ describe('builtin helpers', function() {
return new Iterator(this.arr); return new Iterator(this.arr);
}; };
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
var goodbyes = new Iterable([ var goodbyes = new Iterable([{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]);
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
]);
var goodbyesEmpty = new Iterable([]); var goodbyesEmpty = new Iterable([]);
var hash = {goodbyes: goodbyes, world: 'world'}; var hash = {goodbyes: goodbyes, world: 'world'};
shouldCompileTo( shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
string, 'each with array argument iterates over the contents when not empty');
hash, shouldCompileTo(string, {goodbyes: goodbyesEmpty, world: 'world'}, 'cruel world!',
'goodbye! Goodbye! GOODBYE! cruel world!', 'each with array argument ignores the contents when empty');
'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'
);
}); });
} }
}); });
@@ -559,7 +293,9 @@ describe('builtin helpers', function() {
return; return;
} }
var $log, $info, $error; var $log,
$info,
$error;
beforeEach(function() { beforeEach(function() {
$log = console.log; $log = console.log;
$info = console.info; $info = console.info;
@@ -710,26 +446,10 @@ describe('builtin helpers', function() {
shouldCompileTo(string, hash, ''); shouldCompileTo(string, hash, '');
equals(true, called); equals(true, called);
}); });
it('should pass zero log arguments', function() {
var string = '{{log}}';
var hash = { blah: 'whee' };
var called;
console.info = console.log = function() {
expect(arguments.length).to.equal(0);
called = true;
console.log = $log;
};
expectTemplate(string)
.withInput(hash)
.toCompileTo('');
expect(called).to.be.true();
});
/* eslint-enable no-console */ /* eslint-enable no-console */
}); });
describe('#lookup', function() { describe('#lookup', function() {
it('should lookup arbitrary content', function() { it('should lookup arbitrary content', function() {
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}', var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
+25 -102
View File
@@ -13,84 +13,37 @@ describe('compiler', function() {
equal(compile('foo').equals(compile('foo')), true); equal(compile('foo').equals(compile('foo')), true);
equal(compile('{{foo}}').equals(compile('{{foo}}')), true); equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true); equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
equal( equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
compile('{{foo.bar baz "foo" true false bat=1}}').equals( equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
compile('{{foo.bar baz "foo" true false bat=1}}') equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
),
true
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (baz bat=1)}}')
),
true
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
true
);
}); });
it('should treat as not equal', function() { it('should treat as not equal', function() {
equal(compile('foo').equals(compile('bar')), false); equal(compile('foo').equals(compile('bar')), false);
equal(compile('{{foo}}').equals(compile('{{bar}}')), false); equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false); equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
equal( equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
compile('{{foo.bar baz bat=1}}').equals( equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
compile('{{foo.bar bar bat=1}}') equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
), equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
false
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (bar bat=1)}}')
),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(
compile('{{#foo}} {{foo}}{{/foo}}')
),
false
);
}); });
}); });
describe('#compile', function() { describe('#compile', function() {
it('should fail with invalid input', function() { it('should fail with invalid input', function() {
shouldThrow( shouldThrow(function() {
function() {
Handlebars.compile(null); Handlebars.compile(null);
}, }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
Error, shouldThrow(function() {
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function() {
Handlebars.compile({}); Handlebars.compile({});
}, }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
}); });
it('should include the location in the error (row and column)', function() { it('should include the location in the error (row and column)', function() {
try { try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')(); Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal( equal(true, false, 'Statement must throw exception. This line should not be executed.');
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) { } catch (err) {
equal( equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
err.message,
"if doesn't match def - 2:5",
'Checking error message'
);
if (Object.getOwnPropertyDescriptor(err, 'column').writable) { if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
@@ -104,28 +57,17 @@ describe('compiler', function() {
it('should include the location as enumerable property', function() { it('should include the location as enumerable property', function() {
try { try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')(); Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal( equal(true, false, 'Statement must throw exception. This line should not be executed.');
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) { } catch (err) {
equal( equal(Object.prototype.propertyIsEnumerable.call(err, 'column'), true, 'Checking error column');
Object.prototype.propertyIsEnumerable.call(err, 'column'),
true,
'Checking error column'
);
} }
}); });
it('can utilize AST instance', function() { it('can utilize AST instance', function() {
equal( equal(Handlebars.compile({
Handlebars.compile({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [ {type: 'ContentStatement', value: 'Hello'}]
})(), })(), 'Hello');
'Hello'
);
}); });
it('can pass through an empty string', function() { it('can pass through an empty string', function() {
@@ -135,50 +77,31 @@ describe('compiler', function() {
it('should not modify the options.data property(GH-1327)', function() { it('should not modify the options.data property(GH-1327)', function() {
var options = {data: [{a: 'foo'}, {a: 'bar'}]}; var options = {data: [{a: 'foo'}, {a: 'bar'}]};
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal( equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
JSON.stringify(options, 0, 2),
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
);
}); });
it('should not modify the options.knownHelpers property(GH-1327)', function() { it('should not modify the options.knownHelpers property(GH-1327)', function() {
var options = {knownHelpers: {}}; var options = {knownHelpers: {}};
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal( equal(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
JSON.stringify(options, 0, 2),
JSON.stringify({ knownHelpers: {} }, 0, 2)
);
}); });
}); });
describe('#precompile', function() { describe('#precompile', function() {
it('should fail with invalid input', function() { it('should fail with invalid input', function() {
shouldThrow( shouldThrow(function() {
function() {
Handlebars.precompile(null); Handlebars.precompile(null);
}, }, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
Error, shouldThrow(function() {
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
);
shouldThrow(
function() {
Handlebars.precompile({}); Handlebars.precompile({});
}, }, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
);
}); });
it('can utilize AST instance', function() { it('can utilize AST instance', function() {
equal( equal(/return "Hello"/.test(Handlebars.precompile({
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [ {type: 'ContentStatement', value: 'Hello'}]
}) })), true);
),
true
);
}); });
it('can pass through an empty string', function() { it('can pass through an empty string', function() {
+29 -122
View File
@@ -8,10 +8,7 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
{ noun: 'cat' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper'); equals('happy cat', result, 'Data output by helper');
}); });
@@ -22,9 +19,7 @@ describe('data', function() {
}); });
it('deep @foo triggers automatic top-level data', function() { it('deep @foo triggers automatic top-level data', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
);
var helpers = Handlebars.createFrame(handlebarsEnv.helpers); var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
@@ -52,11 +47,7 @@ describe('data', function() {
}; };
var result = template({}, { helpers: helpers, data: { world: 'world' } }); var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals( equals('Hello world', result, '@foo as a parameter retrieves template data');
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
}); });
it('hash values can be looked up via @foo', function() { it('hash values can be looked up via @foo', function() {
@@ -68,11 +59,7 @@ describe('data', function() {
}; };
var result = template({}, { helpers: helpers, data: { world: 'world' } }); var result = template({}, { helpers: helpers, data: { world: 'world' } });
equals( equals('Hello world', result, '@foo as a parameter retrieves template data');
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
}); });
it('nested parameter data can be looked up via @foo.bar', function() { it('nested parameter data can be looked up via @foo.bar', function() {
@@ -83,15 +70,8 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({}, { helpers: helpers, data: { world: {bar: 'world' } } });
{}, equals('Hello world', result, '@foo as a parameter retrieves template data');
{ helpers: helpers, data: { world: { bar: 'world' } } }
);
equals(
'Hello world',
result,
'@foo as a parameter retrieves template data'
);
}); });
it('nested parameter data does not fail with @world.bar', function() { it('nested parameter data does not fail with @world.bar', function() {
@@ -102,15 +82,8 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({}, { helpers: helpers, data: { foo: {bar: 'world' } } });
{}, equals('Hello undefined', result, '@foo as a parameter retrieves template data');
{ helpers: helpers, data: { foo: { bar: 'world' } } }
);
equals(
'Hello undefined',
result,
'@foo as a parameter retrieves template data'
);
}); });
it('parameter data throws when using complex scope references', function() { it('parameter data throws when using complex scope references', function() {
@@ -123,38 +96,17 @@ describe('data', function() {
it('data can be functions', function() { it('data can be functions', function() {
var template = CompilerContext.compile('{{@hello}}'); var template = CompilerContext.compile('{{@hello}}');
var result = template( var result = template({}, { data: { hello: function() { return 'hello'; } } });
{},
{
data: {
hello: function() {
return 'hello';
}
}
}
);
equals('hello', result); equals('hello', result);
}); });
it('data can be functions with params', function() { it('data can be functions with params', function() {
var template = CompilerContext.compile('{{@hello "hello"}}'); var template = CompilerContext.compile('{{@hello "hello"}}');
var result = template( var result = template({}, { data: { hello: function(arg) { return arg; } } });
{},
{
data: {
hello: function(arg) {
return arg;
}
}
}
);
equals('hello', result); equals('hello', result);
}); });
it('data is inherited downstream', function() { it('data is inherited downstream', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}', { data: true });
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
{ data: true }
);
var helpers = { var helpers = {
let: function(options) { let: function(options) {
var frame = Handlebars.createFrame(options.data); var frame = Handlebars.createFrame(options.data);
@@ -167,10 +119,7 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
{ bar: { baz: 'hello world' } },
{ helpers: helpers, data: {} }
);
equals('2hello world1', result, 'data variables are inherited downstream'); equals('2hello world1', result, 'data variables are inherited downstream');
}); });
@@ -187,10 +136,7 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
{ noun: 'cat' },
{ helpers: helpers, partials: partials, data: { adjective: 'happy' } }
);
equals('happy cat', result, 'Data output by helper inside partial'); equals('happy cat', result, 'Data output by helper inside partial');
}); });
@@ -203,17 +149,12 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
{ exclaim: true, world: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper'); equals('happy world!', result, 'Data output by helper');
}); });
it('passing in data to a compiled function that expects data - works with block helpers', function() { it('passing in data to a compiled function that expects data - works with block helpers', function() {
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', { var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {data: true});
data: true
});
var helpers = { var helpers = {
hello: function(options) { hello: function(options) {
@@ -224,18 +165,12 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
{ exclaim: true },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world!', result, 'Data output by helper'); equals('happy world!', result, 'Data output by helper');
}); });
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() { it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = { var helpers = {
hello: function(options) { hello: function(options) {
@@ -246,18 +181,12 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('happy world?', result, 'Data output by helper'); equals('happy world?', result, 'Data output by helper');
}); });
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() { it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = { var helpers = {
hello: function(options) { hello: function(options) {
@@ -268,43 +197,29 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy', accessData: '#win'}});
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
);
equals('#win happy world?', result, 'Data output by helper'); equals('#win happy world?', result, 'Data output by helper');
}); });
it('you can override inherited data when invoking a helper', function() { it('you can override inherited data when invoking a helper', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#hello}}{{world zomg}}{{/hello}}', {data: true});
'{{#hello}}{{world zomg}}{{/hello}}',
{ data: true }
);
var helpers = { var helpers = {
hello: function(options) { hello: function(options) {
return options.fn( return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
{ exclaim: '?', zomg: 'world' },
{ data: { adjective: 'sad' } }
);
}, },
world: function(thing, options) { world: function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || ''); return options.data.adjective + ' ' + thing + (this.exclaim || '');
} }
}; };
var result = template( var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
{ exclaim: true, zomg: 'planet' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper'); equals('sad world?', result, 'Overriden data output by helper');
}); });
it('you can override inherited data when invoking a helper with depth', function() { it('you can override inherited data when invoking a helper with depth', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
'{{#hello}}{{world ../zomg}}{{/hello}}',
{ data: true }
);
var helpers = { var helpers = {
hello: function(options) { hello: function(options) {
@@ -315,10 +230,7 @@ describe('data', function() {
} }
}; };
var result = template( var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
{ exclaim: true, zomg: 'world' },
{ helpers: helpers, data: { adjective: 'happy' } }
);
equals('sad world?', result, 'Overriden data output by helper'); equals('sad world?', result, 'Overriden data output by helper');
}); });
@@ -340,12 +252,8 @@ describe('data', function() {
describe('nesting', function() { describe('nesting', function() {
it('the root context can be looked up via @root', function() { it('the root context can be looked up via @root', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' var result = template({foo: 'hello'}, {
);
var result = template(
{ foo: 'hello' },
{
helpers: { helpers: {
helper: function(options) { helper: function(options) {
var frame = Handlebars.createFrame(options.data); var frame = Handlebars.createFrame(options.data);
@@ -356,8 +264,7 @@ describe('data', function() {
data: { data: {
depth: 0 depth: 0
} }
} });
);
equals('2 1 0', result); equals('2 1 0', result);
}); });
}); });
+2 -4
View File
@@ -6,6 +6,7 @@ var fs = require('fs'),
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
@@ -17,10 +18,7 @@ var filename = 'dist/handlebars.js';
if (global.minimizedTest) { if (global.minimizedTest) {
filename = 'dist/handlebars.min.js'; filename = 'dist/handlebars.min.js';
} }
var distHandlebars = fs.readFileSync( var distHandlebars = fs.readFileSync(require.resolve('../../' + filename), 'utf-8');
require.resolve('../../' + filename),
'utf-8'
);
vm.runInThisContext(distHandlebars, filename); vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = { global.CompilerContext = {
+20 -50
View File
@@ -1,6 +1,6 @@
var global = (function() { var global = (function() {
return this; return this;
})(); }());
var AssertError; var AssertError;
if (Error.captureStackTrace) { if (Error.captureStackTrace) {
@@ -28,19 +28,10 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
/** /**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead * @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/ */
global.shouldCompileToWithPartials = function shouldCompileToWithPartials( global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
string,
hashOrArray,
partials,
expected,
message
) {
var result = compileWithPartials(string, hashOrArray, partials); var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) { if (result !== expected) {
throw new AssertError( throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
"'" + result + "' should === '" + expected + "': " + message,
shouldCompileToWithPartials
);
} }
}; };
@@ -48,7 +39,9 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead * @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/ */
global.compileWithPartials = function(string, hashOrArray, partials) { global.compileWithPartials = function(string, hashOrArray, partials) {
var template, ary, options; var template,
ary,
options;
if (hashOrArray && hashOrArray.hash) { if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray]; ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash; delete hashOrArray.hash;
@@ -56,10 +49,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
ary = []; ary = [];
ary.push(hashOrArray[0]); // input ary.push(hashOrArray[0]); // input
ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]}); ary.push({helpers: hashOrArray[1], partials: hashOrArray[2]});
options = options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
typeof hashOrArray[3] === 'object'
? hashOrArray[3]
: { compat: hashOrArray[3] };
if (hashOrArray[4] != null) { if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4]; options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4]; ary[1].data = hashOrArray[4];
@@ -68,23 +58,18 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
ary = [hashOrArray]; ary = [hashOrArray];
} }
template = CompilerContext[partials ? 'compileWithPartial' : 'compile']( template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
string,
options
);
return template.apply(this, ary); return template.apply(this, ary);
}; };
/** /**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`) * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/ * @see https://www.chaijs.com/api/bdd/
*/ */
global.equals = global.equal = function equals(a, b, msg) { global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) { if (a !== b) {
throw new AssertError( throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
equals
);
} }
}; };
@@ -101,19 +86,8 @@ global.shouldThrow = function(callback, type, msg) {
if (type && !(caught instanceof type)) { if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught); throw new AssertError('Type failure: ' + caught);
} }
if ( if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
msg && throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
!(msg.test ? msg.test(caught.message) : msg === caught.message)
) {
throw new AssertError(
'Throw mismatch: Expected ' +
caught.message +
' to match ' +
msg +
'\n\n' +
caught.stack,
shouldThrow
);
} }
} }
if (failed) { if (failed) {
@@ -121,17 +95,18 @@ global.shouldThrow = function(callback, type, msg) {
} }
}; };
global.expectTemplate = function(templateAsString) { global.expectTemplate = function(templateAsString) {
return new HandlebarsTestBench(templateAsString); return new HandlebarsTestBench(templateAsString);
}; };
function HandlebarsTestBench(templateAsString) { function HandlebarsTestBench(templateAsString) {
this.templateAsString = templateAsString; this.templateAsString = templateAsString;
this.helpers = {}; this.helpers = {};
this.partials = {}; this.partials = {};
this.input = {}; this.input = {};
this.message = this.message = 'Template' + templateAsString + ' does not evaluate to expected output';
'Template' + templateAsString + ' does not evaluate to expected output';
this.compileOptions = {}; this.compileOptions = {};
this.runtimeOptions = {}; this.runtimeOptions = {};
} }
@@ -167,24 +142,19 @@ HandlebarsTestBench.prototype.withMessage = function(message) {
}; };
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
expect(this._compileAndExecute()).to.equal(expectedOutputAsString); expect(this._compileAndExeute()).to.equal(expectedOutputAsString);
}; };
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) // see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
HandlebarsTestBench.prototype.toThrow = function( HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher, msg) {
errorLike,
errMsgMatcher,
msg
) {
var self = this; var self = this;
expect(function() { expect(function() {
self._compileAndExecute(); self._compileAndExeute();
}).to.throw(errorLike, errMsgMatcher, msg); }).to.throw(errorLike, errMsgMatcher, msg);
}; };
HandlebarsTestBench.prototype._compileAndExecute = function() { HandlebarsTestBench.prototype._compileAndExeute = function() {
var compile = var compile = Object.keys(this.partials).length > 0
Object.keys(this.partials).length > 0
? CompilerContext.compileWithPartial ? CompilerContext.compileWithPartial
: CompilerContext.compile; : CompilerContext.compile;
+1
View File
@@ -3,6 +3,7 @@ require('./common');
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
View File
+8 -8
View File
@@ -13,14 +13,9 @@ if (grep === '--min') {
grep = undefined; grep = undefined;
} }
var files = fs var files = fs.readdirSync(testDir)
.readdirSync(testDir) .filter(function(name) { return (/.*\.js$/).test(name); })
.filter(function(name) { .map(function(name) { return testDir + path.sep + name; });
return /.*\.js$/.test(name);
})
.map(function(name) {
return testDir + path.sep + name;
});
if (global.minimizedTest) { if (global.minimizedTest) {
run('./runtime', function() { run('./runtime', function() {
@@ -42,8 +37,13 @@ if (global.minimizedTest) {
}); });
} }
function run(env, callback) { function run(env, callback) {
if (errors > 0) {
throw new Error('Errors have been detected before');
}
var mocha = new Mocha(); var mocha = new Mocha();
mocha.bail();
mocha.ui('bdd'); mocha.ui('bdd');
mocha.files = files.slice(); mocha.files = files.slice();
if (grep) { if (grep) {
+3 -9
View File
@@ -6,6 +6,7 @@ var fs = require('fs'),
var chai = require('chai'); var chai = require('chai');
var dirtyChai = require('dirty-chai'); var dirtyChai = require('dirty-chai');
chai.use(dirtyChai); chai.use(dirtyChai);
global.expect = chai.expect; global.expect = chai.expect;
@@ -17,10 +18,7 @@ var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) { if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js'; filename = 'dist/handlebars.runtime.min.js';
} }
vm.runInThisContext( vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
fs.readFileSync(__dirname + '/../../' + filename),
filename
);
var parse = require('../../dist/cjs/handlebars/compiler/base').parse; var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler'); var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
@@ -32,11 +30,7 @@ global.CompilerContext = {
compile: function(template, options) { compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests // Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) { handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile( return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
precompileTemplate,
precompileOptions,
handlebarsEnv
);
}; };
handlebarsEnv.parse = parse; handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler; handlebarsEnv.Compiler = compiler.Compiler;
+181 -688
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -28,7 +28,6 @@
mocha.setup('bdd'); mocha.setup('bdd');
</script> </script>
<script src="/dist/handlebars.js"></script> <script src="/dist/handlebars.js"></script>
<script src="/spec/vendor/json2.js"></script>
<script src="/spec/env/common.js"></script> <script src="/spec/env/common.js"></script>
<script> <script>
var CompilerContext = { var CompilerContext = {
+4 -35
View File
@@ -13,10 +13,7 @@ describe('javascript-compiler api', function() {
}); });
it('should allow override', function() { it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function( handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
parent,
name
) {
return parent + '.bar_' + name; return parent + '.bar_' + name;
}; };
/* eslint-disable camelcase */ /* eslint-disable camelcase */
@@ -46,7 +43,7 @@ describe('javascript-compiler api', function() {
}; };
handlebarsEnv.VM.checkRevision = function(compilerInfo) { handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') { if (compilerInfo !== 'crazy') {
throw new Error("It didn't work"); throw new Error('It didn\'t work');
} }
}; };
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food '); shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
@@ -57,8 +54,7 @@ describe('javascript-compiler api', function() {
beforeEach(function() { beforeEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true; handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer; $superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate = $superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
}); });
afterEach(function() { afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false; handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
@@ -73,37 +69,10 @@ describe('javascript-compiler api', function() {
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food '); shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
}); });
it('should allow append buffer override', function() { it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function( handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
string
) {
return $superAppend.call(this, [string, ' + "_foo"']); return $superAppend.call(this, [string, ' + "_foo"']);
}; };
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo'); shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
}); });
}); });
describe('#isValidJavaScriptVariableName', function() {
// It is there and accessible and could be used by someone. That's why we don't remove it
// it 4.x. But if we keep it, we add a test
// This test should not encourage you to use the function. It is not needed any more
// and might be removed in 5.0
['test', 'abc123', 'abc_123'].forEach(function(validVariableName) {
it("should return true for '" + validVariableName + "'", function() {
expect(
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
validVariableName
)
).to.be.true();
});
});
[('123test', 'abc()', 'abc.cde')].forEach(function(invalidVariableName) {
it("should return true for '" + invalidVariableName + "'", function() {
expect(
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
invalidVariableName
)
).to.be.false();
});
});
});
}); });
+74 -236
View File
@@ -68,10 +68,7 @@ describe('parser', function() {
equals(astFor('{{null}}'), '{{ NULL [] }}\n'); equals(astFor('{{null}}'), '{{ NULL [] }}\n');
}); });
it('parses mustaches with undefined and null parameters', function() { it('parses mustaches with undefined and null parameters', function() {
equals( equals(astFor('{{foo undefined null}}'), '{{ PATH:foo [UNDEFINED, NULL] }}\n');
astFor('{{foo undefined null}}'),
'{{ PATH:foo [UNDEFINED, NULL] }}\n'
);
}); });
it('parses mustaches with DATA parameters', function() { it('parses mustaches with DATA parameters', function() {
@@ -81,53 +78,23 @@ describe('parser', function() {
it('parses mustaches with hash arguments', function() { it('parses mustaches with hash arguments', function() {
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n'); equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n'); equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
equals( equals(astFor('{{foo bar=true}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n');
astFor('{{foo bar=true}}'), equals(astFor('{{foo bar=false}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n');
'{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n' equals(astFor('{{foo bar=@baz}}'), '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\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( equals(astFor('{{foo bar=baz bat=bam}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n');
astFor('{{foo bar=baz bat=bam}}'), equals(astFor('{{foo bar=baz bat="bam"}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n');
'{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
);
equals(
astFor('{{foo bar=baz bat="bam"}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n'); equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
equals( equals(astFor('{{foo omg bar=baz bat="bam"}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n');
astFor('{{foo omg bar=baz bat="bam"}}'), equals(astFor('{{foo omg bar=baz bat="bam" baz=1}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n');
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\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" baz=1}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
);
}); });
it('parses contents followed by a mustache', function() { it('parses contents followed by a mustache', function() {
equals( equals(astFor('foo bar {{baz}}'), 'CONTENT[ \'foo bar \' ]\n{{ PATH:baz [] }}\n');
astFor('foo bar {{baz}}'),
"CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
);
}); });
it('parses a partial', function() { it('parses a partial', function() {
@@ -141,81 +108,47 @@ describe('parser', function() {
}); });
it('parses a partial with hash', function() { it('parses a partial with hash', function() {
equals( equals(astFor('{{> foo bar=bat}}'), '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n');
astFor('{{> foo bar=bat}}'),
'{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
);
}); });
it('parses a partial with context and hash', function() { it('parses a partial with context and hash', function() {
equals( equals(astFor('{{> foo bar bat=baz}}'), '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n');
astFor('{{> foo bar bat=baz}}'),
'{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
);
}); });
it('parses a partial with a complex name', function() { it('parses a partial with a complex name', function() {
equals( equals(astFor('{{> shared/partial?.bar}}'), '{{> PARTIAL:shared/partial?.bar }}\n');
astFor('{{> shared/partial?.bar}}'),
'{{> PARTIAL:shared/partial?.bar }}\n'
);
}); });
it('parsers partial blocks', function() { it('parsers partial blocks', function() {
equals( equals(astFor('{{#> foo}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
astFor('{{#> foo}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
}); });
it('should handle parser block mismatch', function() { it('should handle parser block mismatch', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('{{#> goodbyes}}{{/hellos}}'); astFor('{{#> goodbyes}}{{/hellos}}');
}, }, Error, (/goodbyes doesn't match hellos/));
Error,
/goodbyes doesn't match hellos/
);
}); });
it('parsers partial blocks with arguments', function() { it('parsers partial blocks with arguments', function() {
equals( equals(astFor('{{#> foo context hash=value}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
astFor('{{#> foo context hash=value}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
}); });
it('parses a comment', function() { it('parses a comment', function() {
equals( equals(astFor('{{! this is a comment }}'), "{{! ' this is a comment ' }}\n");
astFor('{{! this is a comment }}'),
"{{! ' this is a comment ' }}\n"
);
}); });
it('parses a multi-line comment', function() { it('parses a multi-line comment', function() {
equals( equals(astFor('{{!\nthis is a multi-line comment\n}}'), "{{! '\nthis is a multi-line comment\n' }}\n");
astFor('{{!\nthis is a multi-line comment\n}}'),
"{{! '\nthis is a multi-line comment\n' }}\n"
);
}); });
it('parses an inverse section', function() { it('parses an inverse section', function() {
equals( equals(astFor('{{#foo}} bar {{^}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
}); });
it('parses an inverse (else-style) section', function() { it('parses an inverse (else-style) section', function() {
equals( equals(astFor('{{#foo}} bar {{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
}); });
it('parses multiple inverse sections', function() { it('parses multiple inverse sections', function() {
equals( 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");
astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
}); });
it('parses empty blocks', function() { it('parses empty blocks', function() {
@@ -223,52 +156,31 @@ describe('parser', function() {
}); });
it('parses empty blocks with empty inverse section', function() { it('parses empty blocks with empty inverse section', function() {
equals( equals(astFor('{{#foo}}{{^}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
astFor('{{#foo}}{{^}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
}); });
it('parses empty blocks with empty inverse (else-style) section', function() { it('parses empty blocks with empty inverse (else-style) section', function() {
equals( equals(astFor('{{#foo}}{{else}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
astFor('{{#foo}}{{else}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
}); });
it('parses non-empty blocks with empty inverse section', function() { it('parses non-empty blocks with empty inverse section', function() {
equals( equals(astFor('{{#foo}} bar {{^}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
astFor('{{#foo}} bar {{^}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
}); });
it('parses non-empty blocks with empty inverse (else-style) section', function() { it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals( equals(astFor('{{#foo}} bar {{else}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
astFor('{{#foo}} bar {{else}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
}); });
it('parses empty blocks with non-empty inverse section', function() { it('parses empty blocks with non-empty inverse section', function() {
equals( equals(astFor('{{#foo}}{{^}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
astFor('{{#foo}}{{^}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
}); });
it('parses empty blocks with non-empty inverse (else-style) section', function() { it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals( equals(astFor('{{#foo}}{{else}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
astFor('{{#foo}}{{else}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
}); });
it('parses a standalone inverse section', function() { it('parses a standalone inverse section', function() {
equals( equals(astFor('{{^foo}}bar{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n");
astFor('{{^foo}}bar{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
);
}); });
it('throws on old inverse section', function() { it('throws on old inverse section', function() {
shouldThrow(function() { shouldThrow(function() {
@@ -277,179 +189,105 @@ describe('parser', function() {
}); });
it('parses block with block params', function() { it('parses block with block params', function() {
equals( equals(astFor('{{#foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
astFor('{{#foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
}); });
it('parses inverse block with block params', function() { it('parses inverse block with block params', function() {
equals( equals(astFor('{{^foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
astFor('{{^foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
}); });
it('parses chained inverse block with block params', function() { it('parses chained inverse block with block params', function() {
equals( 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");
astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
}); });
it("raises if there's a Parse error", function() { it('raises if there\'s a Parse error', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('foo{{^}}bar'); astFor('foo{{^}}bar');
}, }, Error, /Parse error on line 1/);
Error, shouldThrow(function() {
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo}'); astFor('{{foo}');
}, }, Error, /Parse error on line 1/);
Error, shouldThrow(function() {
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo &}}'); astFor('{{foo &}}');
}, }, Error, /Parse error on line 1/);
Error, shouldThrow(function() {
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{#goodbyes}}{{/hellos}}'); astFor('{{#goodbyes}}{{/hellos}}');
}, }, Error, /goodbyes doesn't match hellos/);
Error,
/goodbyes doesn't match hellos/
);
shouldThrow( shouldThrow(function() {
function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}'); astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
}, }, Error, /goodbyes doesn't match hellos/);
Error,
/goodbyes doesn't match hellos/
);
}); });
it('should handle invalid paths', function() { it('should handle invalid paths', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('{{foo/../bar}}'); astFor('{{foo/../bar}}');
}, }, Error, /Invalid path: foo\/\.\. - 1:2/);
Error, shouldThrow(function() {
/Invalid path: foo\/\.\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/./bar}}'); astFor('{{foo/./bar}}');
}, }, Error, /Invalid path: foo\/\. - 1:2/);
Error, shouldThrow(function() {
/Invalid path: foo\/\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/this/bar}}'); astFor('{{foo/this/bar}}');
}, }, Error, /Invalid path: foo\/this - 1:2/);
Error,
/Invalid path: foo\/this - 1:2/
);
}); });
it('knows how to report the correct line number in errors', function() { it('knows how to report the correct line number in errors', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('hello\nmy\n{{foo}'); astFor('hello\nmy\n{{foo}');
}, }, Error, /Parse error on line 3/);
Error, shouldThrow(function() {
/Parse error on line 3/
);
shouldThrow(
function() {
astFor('hello\n\nmy\n\n{{foo}'); astFor('hello\n\nmy\n\n{{foo}');
}, }, Error, /Parse error on line 5/);
Error,
/Parse error on line 5/
);
}); });
it('knows how to report the correct line number in errors when the first character is a newline', function() { it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('\n\nhello\n\nmy\n\n{{foo}'); astFor('\n\nhello\n\nmy\n\n{{foo}');
}, }, Error, /Parse error on line 7/);
Error,
/Parse error on line 7/
);
}); });
describe('externally compiled AST', function() { describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() { it('can pass through an already-compiled AST', function() {
equals( equals(astFor({
astFor({
type: 'Program', type: 'Program',
body: [ {type: 'ContentStatement', value: 'Hello'}] body: [ {type: 'ContentStatement', value: 'Hello'}]
}), }), 'CONTENT[ \'Hello\' ]\n');
"CONTENT[ 'Hello' ]\n"
);
}); });
}); });
describe('directives', function() { describe('directives', function() {
it('should parse block directives', function() { it('should parse block directives', function() {
equals( equals(astFor('{{#* foo}}{{/foo}}'), 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
astFor('{{#* foo}}{{/foo}}'),
'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
);
}); });
it('should parse directives', function() { it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n'); equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
}); });
it('should fail if directives have inverse', function() { it('should fail if directives have inverse', function() {
shouldThrow( shouldThrow(function() {
function() {
astFor('{{#* foo}}{{^}}{{/foo}}'); astFor('{{#* foo}}{{^}}{{/foo}}');
}, }, Error, /Unexpected inverse/);
Error,
/Unexpected inverse/
);
}); });
}); });
it('GH1024 - should track program location properly', function() { it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse( var p = Handlebars.parse('\n'
'\n' + + ' {{#if foo}}\n'
' {{#if foo}}\n' + + ' {{bar}}\n'
' {{bar}}\n' + + ' {{else}} {{baz}}\n'
' {{else}} {{baz}}\n' + + '\n'
'\n' + + ' {{/if}}\n'
' {{/if}}\n' + + ' ');
' '
);
// We really need a deep equals but for now this should be stable... // We really need a deep equals but for now this should be stable...
equals( equals(JSON.stringify(p.loc), JSON.stringify({
JSON.stringify(p.loc),
JSON.stringify({
start: { line: 1, column: 0 }, start: { line: 1, column: 0 },
end: { line: 7, column: 4 } end: { line: 7, column: 4 }
}) }));
); equals(JSON.stringify(p.body[1].program.loc), JSON.stringify({
equals(
JSON.stringify(p.body[1].program.loc),
JSON.stringify({
start: { line: 2, column: 13 }, start: { line: 2, column: 13 },
end: { line: 4, column: 7 } end: { line: 4, column: 7 }
}) }));
); equals(JSON.stringify(p.body[1].inverse.loc), JSON.stringify({
equals(
JSON.stringify(p.body[1].inverse.loc),
JSON.stringify({
start: { line: 4, column: 15 }, start: { line: 4, column: 15 },
end: { line: 6, column: 5 } end: { line: 6, column: 5 }
}) }));
);
}); });
}); });
+105 -447
View File
@@ -2,224 +2,115 @@ describe('partials', function() {
it('basic partials', function() { it('basic partials', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; var partial = '{{name}} ({{url}}) ';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
{ name: 'Yehuda', url: 'http://yehuda' }, shouldCompileToWithPartials(string, [hash, {}, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
}); });
it('dynamic partials', function() { it('dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; var partial = '{{name}} ({{url}}) ';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = { var helpers = {
partial: function() { partial: function() {
return 'dude'; return 'dude';
} }
}; };
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
string, shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
}); });
it('failing dynamic partials', function() { it('failing dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) '; var partial = '{{name}} ({{url}}) ';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = { var helpers = {
partial: function() { partial: function() {
return 'missing'; return 'missing';
} }
}; };
shouldThrow( shouldThrow(function() {
function() { shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
shouldCompileToWithPartials( }, Handlebars.Exception, 'The partial missing could not be found');
string,
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
},
Handlebars.Exception,
'The partial missing could not be found'
);
}); });
it('partials with context', function() { it('partials with context', function() {
var string = 'Dudes: {{>dude dudes}}'; var string = 'Dudes: {{>dude dudes}}';
var partial = '{{#this}}{{name}} ({{url}}) {{/this}}'; var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
{ name: 'Yehuda', url: 'http://yehuda' }, 'Partials can be passed a context');
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Partials can be passed a context'
);
}); });
it('partials with no context', function() { it('partials with no context', function() {
var partial = '{{name}} ({{url}}) '; var partial = '{{name}} ({{url}}) ';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials( shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}', 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}], [hash, {}, {dude: partial}, {explicitPartialContext: true}],
true, true,
'Dudes: () () ' 'Dudes: () () ');
);
shouldCompileToWithPartials( shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}', 'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, {dude: partial}, {explicitPartialContext: true}], [hash, {}, {dude: partial}, {explicitPartialContext: true}],
true, true,
'Dudes: foo () foo () ' 'Dudes: foo () foo () ');
);
}); });
it('partials with string context', function() { it('partials with string context', function() {
var string = 'Dudes: {{>dude "dudes"}}'; var string = 'Dudes: {{>dude "dudes"}}';
var partial = '{{.}}'; var partial = '{{.}}';
var hash = {}; var hash = {};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: dudes');
string,
[hash, {}, { dude: partial }],
true,
'Dudes: dudes'
);
}); });
it('partials with undefined context', function() { it('partials with undefined context', function() {
var string = 'Dudes: {{>dude dudes}}'; var string = 'Dudes: {{>dude dudes}}';
var partial = '{{foo}} Empty'; var partial = '{{foo}} Empty';
var hash = {}; var hash = {};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Empty');
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Empty'
);
}); });
it('partials with duplicate parameters', function() { it('partials with duplicate parameters', function() {
shouldThrow( shouldThrow(function() {
function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}'); CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
}, }, Error, 'Unsupported number of partial arguments: 2 - 1:7');
Error,
'Unsupported number of partial arguments: 2 - 1:7'
);
}); });
it('partials with parameters', function() { it('partials with parameters', function() {
var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
var partial = '{{others.foo}}{{name}} ({{url}}) '; var partial = '{{others.foo}}{{name}} ({{url}}) ';
var hash = { var hash = {foo: 'bar', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
foo: 'bar', shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
dudes: [ 'Basic partials output based on current context.');
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
'Basic partials output based on current context.'
);
}); });
it('partial in a partial', function() { it('partial in a partial', function() {
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
var dude = '{{name}} {{> url}} '; var dude = '{{name}} {{> url}} ';
var url = '<a href="{{url}}">{{url}}</a>'; var url = '<a href="{{url}}">{{url}}</a>';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ 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');
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ',
'Partials are rendered inside of other partials'
);
}); });
it('rendering undefined partial throws an exception', function() { it('rendering undefined partial throws an exception', function() {
shouldThrow( shouldThrow(function() {
function() {
var template = CompilerContext.compile('{{> whatever}}'); var template = CompilerContext.compile('{{> whatever}}');
template(); template();
}, }, Handlebars.Exception, 'The partial whatever could not be found');
Handlebars.Exception,
'The partial whatever could not be found'
);
}); });
it('registering undefined partial throws an exception', function() { it('registering undefined partial throws an exception', function() {
shouldThrow( shouldThrow(function() {
function() {
var undef; var undef;
handlebarsEnv.registerPartial('undefined_test', undef); handlebarsEnv.registerPartial('undefined_test', undef);
}, }, Handlebars.Exception, 'Attempting to register a partial called "undefined_test" as undefined');
Handlebars.Exception,
'Attempting to register a partial called "undefined_test" as undefined'
);
}); });
it('rendering template partial in vm mode throws an exception', function() { it('rendering template partial in vm mode throws an exception', function() {
shouldThrow( shouldThrow(function() {
function() {
var template = CompilerContext.compile('{{> whatever}}'); var template = CompilerContext.compile('{{> whatever}}');
template(); template();
}, }, Handlebars.Exception, 'The partial whatever could not be found');
Handlebars.Exception,
'The partial whatever could not be found'
);
}); });
it('rendering function partial in vm mode', function() { it('rendering function partial in vm mode', function() {
@@ -227,57 +118,30 @@ describe('partials', function() {
function partial(context) { function partial(context) {
return context.name + ' (' + context.url + ') '; return context.name + ' (' + context.url + ') ';
} }
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileTo(string, [hash, {}, {dude: partial}], 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
{ name: 'Yehuda', url: 'http://yehuda' }, 'Function partials output based in VM.');
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileTo(
string,
[hash, {}, { dude: partial }],
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Function partials output based in VM.'
);
}); });
it('GH-14: a partial preceding a selector', function() { it('GH-14: a partial preceding a selector', function() {
var string = 'Dudes: {{>dude}} {{anotherDude}}'; var string = 'Dudes: {{>dude}} {{anotherDude}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, 'Dudes: Jeepers Creepers', 'Regular selectors can follow a partial');
string,
[hash, {}, { dude: dude }],
true,
'Dudes: Jeepers Creepers',
'Regular selectors can follow a partial'
);
}); });
it('Partials with slash paths', function() { it('Partials with slash paths', function() {
var string = 'Dudes: {{> shared/dude}}'; var string = 'Dudes: {{> shared/dude}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with slash and point paths', function() { it('Partials with slash and point paths', function() {
var string = 'Dudes: {{> shared/dude.thing}}'; var string = 'Dudes: {{> shared/dude.thing}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing': dude}], true, 'Dudes: Jeepers', 'Partials can use literal with points in paths');
string,
[hash, {}, { 'shared/dude.thing': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal with points in paths'
);
}); });
it('Global Partials', function() { it('Global Partials', function() {
@@ -286,13 +150,7 @@ describe('partials', function() {
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
handlebarsEnv.unregisterPartial('globalTest'); handlebarsEnv.unregisterPartial('globalTest');
equals(handlebarsEnv.partials.globalTest, undefined); equals(handlebarsEnv.partials.globalTest, undefined);
@@ -306,94 +164,50 @@ describe('partials', function() {
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}'; var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
string,
[hash],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
}); });
it('Partials with integer path', function() { it('Partials with integer path', function() {
var string = 'Dudes: {{> 404}}'; var string = 'Dudes: {{> 404}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {404: dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
string,
[hash, {}, { 404: dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with complex path', function() { it('Partials with complex path', function() {
var string = 'Dudes: {{> 404/asdf?.bar}}'; var string = 'Dudes: {{> 404/asdf?.bar}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
string,
[hash, {}, { '404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with escaped', function() { it('Partials with escaped', function() {
var string = 'Dudes: {{> [+404/asdf?.bar]}}'; var string = 'Dudes: {{> [+404/asdf?.bar]}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('Partials with string', function() { it('Partials with string', function() {
var string = "Dudes: {{> '+404/asdf?.bar'}}"; var string = 'Dudes: {{> \'+404/asdf?.bar\'}}';
var dude = '{{name}}'; var dude = '{{name}}';
var hash = {name: 'Jeepers', anotherDude: 'Creepers'}; var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
}); });
it('should handle empty partial', function() { it('should handle empty partial', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = ''; var partial = '';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: ');
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: '
);
}); });
it('throw on missing partial', function() { it('throw on missing partial', function() {
var compile = handlebarsEnv.compile; var compile = handlebarsEnv.compile;
handlebarsEnv.compile = undefined; handlebarsEnv.compile = undefined;
shouldThrow( shouldThrow(function() {
function() {
shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], ''); shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], '');
}, }, Error, /The partial dude could not be compiled/);
Error,
/The partial dude could not be compiled/
);
handlebarsEnv.compile = compile; handlebarsEnv.compile = compile;
}); });
@@ -403,24 +217,21 @@ describe('partials', function() {
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {}], [{}, {}, {}],
true, true,
'success' 'success');
);
}); });
it('should execute default block with proper context', function() { it('should execute default block with proper context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude context}}{{value}}{{/dude}}', '{{#> dude context}}{{value}}{{/dude}}',
[{context: {value: 'success'}}, {}, {}], [{context: {value: 'success'}}, {}, {}],
true, true,
'success' 'success');
);
}); });
it('should propagate block parameters to default block', function() { it('should propagate block parameters to default block', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{context: {value: 'success'}}, {}, {}], [{context: {value: 'success'}}, {}, {}],
true, true,
'success' 'success');
);
}); });
it('should not use partial block if partial exists', function() { it('should not use partial block if partial exists', function() {
@@ -428,8 +239,7 @@ describe('partials', function() {
'{{#> dude}}fail{{/dude}}', '{{#> dude}}fail{{/dude}}',
[{}, {}, {dude: 'success'}], [{}, {}, {dude: 'success'}],
true, true,
'success' 'success');
);
}); });
it('should render block from partial', function() { it('should render block from partial', function() {
@@ -437,44 +247,29 @@ describe('partials', function() {
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }}'}], [{}, {}, {dude: '{{> @partial-block }}'}],
true, true,
'success' 'success');
);
}); });
it('should be able to render the partial-block twice', function() { it('should be able to render the partial-block twice', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}', '{{#> dude}}success{{/dude}}',
[{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}], [{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}],
true, true,
'success success' 'success success');
);
}); });
it('should render block from partial with context', function() { it('should render block from partial with context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}', '{{#> dude}}{{value}}{{/dude}}',
[ [{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true, true,
'success' 'success');
);
}); });
it('should be able to access the @data frame from a partial-block', function() { it('should be able to access the @data frame from a partial-block', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}in-block: {{@root/value}}{{/dude}}', '{{#> dude}}in-block: {{@root/value}}{{/dude}}',
[ [{value: 'success'}, {}, {dude: '<code>before-block: {{@root/value}} {{> @partial-block }}</code>'}],
{ value: 'success' },
{},
{
dude:
'<code>before-block: {{@root/value}} {{> @partial-block }}</code>'
}
],
true, true,
'<code>before-block: success in-block: success</code>' '<code>before-block: success in-block: success</code>');
);
}); });
it('should allow the #each-helper to be used along with partial-blocks', function() { it('should allow the #each-helper to be used along with partial-blocks', function() {
@@ -484,13 +279,11 @@ describe('partials', function() {
{value: ['a', 'b', 'c']}, {value: ['a', 'b', 'c']},
{}, {},
{ {
list: list: '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
'<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
} }
], ],
true, true,
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>' '<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>');
);
}); });
it('should render block from partial with context (twice)', function() { it('should render block from partial with context (twice)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -499,37 +292,25 @@ describe('partials', function() {
{context: {value: 'success'}}, {context: {value: 'success'}},
{}, {},
{ {
dude: dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
'{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
} }
], ],
true, true,
'success success' 'success success');
);
}); });
it('should render block from partial with context', function() { it('should render block from partial with context', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{../context/value}}{{/dude}}', '{{#> dude}}{{../context/value}}{{/dude}}',
[ [{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true, true,
'success' 'success');
);
}); });
it('should render block from partial with block params', function() { it('should render block from partial with block params', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[ [{context: {value: 'success'}}, {}, {dude: '{{> @partial-block }}'}],
{ context: { value: 'success' } },
{},
{ dude: '{{> @partial-block }}' }
],
true, true,
'success' 'success');
);
}); });
it('should render nested partial blocks', function() { it('should render nested partial blocks', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -538,14 +319,12 @@ describe('partials', function() {
{value: 'success'}, {value: 'success'},
{}, {},
{ {
outer: outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, true,
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>' '<template><outer><nested><outer-block>success</outer-block></nested></outer></template>');
);
}); });
it('should render nested partial blocks at different nesting levels', function() { it('should render nested partial blocks at different nesting levels', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -554,14 +333,12 @@ describe('partials', function() {
{value: 'success'}, {value: 'success'},
{}, {},
{ {
outer: outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
'<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, true,
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>' '<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>');
);
}); });
it('should render nested partial blocks at different nesting levels (twice)', function() { it('should render nested partial blocks at different nesting levels (twice)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -570,14 +347,12 @@ describe('partials', function() {
{value: 'success'}, {value: 'success'},
{}, {},
{ {
outer: outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
nested: '<nested>{{> @partial-block}}</nested>' nested: '<nested>{{> @partial-block}}</nested>'
} }
], ],
true, true,
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>' '<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>');
);
}); });
it('should render nested partial blocks (twice at each level)', function() { it('should render nested partial blocks (twice at each level)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -586,79 +361,38 @@ describe('partials', function() {
{value: 'success'}, {value: 'success'},
{}, {},
{ {
outer: outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
'<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>' nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
} }
], ],
true, true,
'<template><outer>' + '<template><outer>' +
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' + '<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
'</outer></template>' '</outer></template>');
);
}); });
}); });
describe('inline partials', function() { describe('inline partials', function() {
it('should define inline partials for template', function() { it('should define inline partials for template', function() {
shouldCompileTo( shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
}); });
it('should overwrite multiple partials in the same template', function() { it('should overwrite multiple partials in the same template', function() {
shouldCompileTo( shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
'{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
}); });
it('should define inline partials for block', function() { it('should define inline partials for block', function() {
shouldCompileTo( shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', shouldThrow(function() {
{}, shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', {}, 'success');
'success' }, Error, /myPartial could not/);
);
shouldThrow(
function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
{},
'success'
);
},
Error,
/myPartial could not/
);
}); });
it('should override global partials', function() { it('should override global partials', function() {
shouldCompileTo( shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {hash: {}, partials: {myPartial: function() { return 'fail'; }}}, 'success');
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{
hash: {},
partials: {
myPartial: function() {
return 'fail';
}
}
},
'success'
);
}); });
it('should override template partials', function() { it('should override template partials', function() {
shouldCompileTo( shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
'{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
}); });
it('should override partials down the entire stack', function() { it('should override partials down the entire stack', function() {
shouldCompileTo( shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', {}, 'success');
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}',
{},
'success'
);
}); });
it('should define inline partials for partial call', function() { it('should define inline partials for partial call', function() {
@@ -666,16 +400,14 @@ describe('partials', function() {
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}', '{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, {dude: '{{> myPartial }}'}], [{}, {}, {dude: '{{> myPartial }}'}],
true, true,
'success' 'success');
);
}); });
it('should define inline partials in partial block call', function() { it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}', '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, {dude: '{{> myPartial }}'}], [{}, {}, {dude: '{{> myPartial }}'}],
true, true,
'success' 'success');
);
}); });
it('should render nested inline partials', function() { it('should render nested inline partials', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -684,8 +416,7 @@ describe('partials', function() {
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{value: 'success'}, {}, {}],
true, true,
'<inner><outer-block>success</outer-block></inner>' '<inner><outer-block>success</outer-block></inner>');
);
}); });
it('should render nested inline partials with partial-blocks on different nesting levels', function() { it('should render nested inline partials with partial-blocks on different nesting levels', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -694,8 +425,7 @@ describe('partials', function() {
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{value: 'success'}, {}, {}],
true, true,
'<inner><outer-block>success</outer-block></inner>success' '<inner><outer-block>success</outer-block></inner>success');
);
}); });
it('should render nested inline partials (twice at each level)', function() { it('should render nested inline partials (twice at each level)', function() {
shouldCompileToWithPartials( shouldCompileToWithPartials(
@@ -704,8 +434,7 @@ describe('partials', function() {
'{{#>outer}}{{value}}{{/outer}}', '{{#>outer}}{{value}}{{/outer}}',
[{value: 'success'}, {}, {}], [{value: 'success'}, {}, {}],
true, true,
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>' '<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>');
);
}); });
}); });
@@ -722,52 +451,25 @@ describe('partials', function() {
it('indented partials', function() { it('indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n'; var dude = '{{name}}\n';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true,
{ name: 'Yehuda', url: 'http://yehuda' }, 'Dudes:\n Yehuda\n Alan\n');
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes:\n Yehuda\n Alan\n'
);
}); });
it('nested indented partials', function() { it('nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}'; var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n'; var url = '{{url}}!\n';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true,
{ name: 'Yehuda', url: 'http://yehuda' }, 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'
);
}); });
it('prevent nested indented partials', function() { it('prevent nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}'; var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}'; var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n'; var url = '{{url}}!\n';
var hash = { var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
dudes: [ shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}, {preventIndent: true}], true,
{ name: 'Yehuda', url: 'http://yehuda' }, 'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
{ 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'
);
}); });
}); });
@@ -775,70 +477,26 @@ describe('partials', function() {
it('partials can access parents', function() { it('partials can access parents', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = { var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
root: 'yes', shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
}); });
it('partials can access parents with custom context', function() { it('partials can access parents with custom context', function() {
var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = { var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
root: 'yes', shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
}); });
it('partials can access parents without data', function() { it('partials can access parents without data', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}'; var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} '; var partial = '{{name}} ({{url}}) {{root}} ';
var hash = { var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
root: 'yes', shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true, false], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true, false],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
}); });
it('partials inherit compat', function() { it('partials inherit compat', function() {
var string = 'Dudes: {{> dude}}'; var string = 'Dudes: {{> dude}}';
var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}'; var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
var hash = { var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
root: 'yes', shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) 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 '
);
}); });
}); });
}); });
+43 -136
View File
@@ -14,13 +14,16 @@ describe('precompiler', function() {
logFunction, logFunction,
errorLog, errorLog,
errorLogFunction, errorLogFunction,
precompile, precompile,
minify, minify,
emptyTemplate = { emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars', path: __dirname + '/artifacts/empty.handlebars',
name: 'empty', name: 'empty',
source: '' source: ''
}, },
file, file,
content, content,
writeFileSync; writeFileSync;
@@ -88,161 +91,97 @@ describe('precompiler', function() {
equals(log, Handlebars.VERSION); equals(log, Handlebars.VERSION);
}); });
it('should throw if lacking templates', function() { it('should throw if lacking templates', function() {
shouldThrow( shouldThrow(function() {
function() {
Precompiler.cli({templates: []}); Precompiler.cli({templates: []});
}, }, Handlebars.Exception, 'Must define at least one template or directory.');
Handlebars.Exception,
'Must define at least one template or directory.'
);
}); });
it('should handle empty/filtered directories', function() { it('should handle empty/filtered directories', function() {
Precompiler.cli({hasDirectory: true, templates: []}); Precompiler.cli({hasDirectory: true, templates: []});
// Success is not throwing // Success is not throwing
}); });
it('should throw when combining simple and minimized', function() { it('should throw when combining simple and minimized', function() {
shouldThrow( shouldThrow(function() {
function() {
Precompiler.cli({templates: [__dirname], simple: true, min: true}); Precompiler.cli({templates: [__dirname], simple: true, min: true});
}, }, Handlebars.Exception, 'Unable to minimize simple output');
Handlebars.Exception,
'Unable to minimize simple output'
);
}); });
it('should throw when combining simple and multiple templates', function() { it('should throw when combining simple and multiple templates', function() {
shouldThrow( shouldThrow(function() {
function() { Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
Precompiler.cli({ }, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
templates: [
__dirname + '/artifacts/empty.handlebars',
__dirname + '/artifacts/empty.handlebars'
],
simple: true
});
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
}); });
it('should throw when missing name', function() { it('should throw when missing name', function() {
shouldThrow( shouldThrow(function() {
function() {
Precompiler.cli({templates: [{source: ''}], amd: true}); Precompiler.cli({templates: [{source: ''}], amd: true});
}, }, Handlebars.Exception, 'Name missing for template');
Handlebars.Exception,
'Name missing for template'
);
}); });
it('should throw when combining simple and directories', function() { it('should throw when combining simple and directories', function() {
shouldThrow( shouldThrow(function() {
function() {
Precompiler.cli({hasDirectory: true, templates: [1], simple: true}); Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
}, }, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
}); });
it('should output simple templates', function() { it('should output simple templates', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'simple'; };
return 'simple';
};
Precompiler.cli({templates: [emptyTemplate], simple: true}); Precompiler.cli({templates: [emptyTemplate], simple: true});
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should default to simple templates', function() { it('should default to simple templates', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'simple'; };
return 'simple';
};
Precompiler.cli({templates: [{source: ''}]}); Precompiler.cli({templates: [{source: ''}]});
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should output amd templates', function() { it('should output amd templates', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd';
};
Precompiler.cli({templates: [emptyTemplate], amd: true}); Precompiler.cli({templates: [emptyTemplate], amd: true});
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output multiple amd', function() { it('should output multiple amd', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd'; Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'});
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
namespace: 'foo'
});
equal(/templates = foo = foo \|\|/.test(log), true); equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true); equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output amd partials', function() { it('should output amd partials', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd';
};
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true}); Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true});
equal(/return Handlebars\.partials\['empty'\]/.test(log), true); equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output multiple amd partials', function() { it('should output multiple amd partials', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd'; Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true});
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
partial: true
});
equal(/return Handlebars\.partials\[/.test(log), false); equal(/return Handlebars\.partials\[/.test(log), false);
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
}); });
it('should output commonjs templates', function() { it('should output commonjs templates', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'commonjs'; };
return 'commonjs';
};
Precompiler.cli({templates: [emptyTemplate], commonjs: true}); Precompiler.cli({templates: [emptyTemplate], commonjs: true});
equal(/template\(commonjs\)/.test(log), true); equal(/template\(commonjs\)/.test(log), true);
}); });
it('should set data flag', function() { it('should set data flag', function() {
Handlebars.precompile = function(data, options) { Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
equal(options.data, true);
return 'simple';
};
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true}); Precompiler.cli({templates: [emptyTemplate], simple: true, data: true});
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should set known helpers', function() { it('should set known helpers', function() {
Handlebars.precompile = function(data, options) { Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
equal(options.knownHelpers.foo, true);
return 'simple';
};
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'}); Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'});
equal(log, 'simple\n'); equal(log, 'simple\n');
}); });
it('should output to file system', function() { it('should output to file system', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'simple'; };
return 'simple'; Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'});
};
Precompiler.cli({
templates: [emptyTemplate],
simple: true,
output: 'file!'
});
equal(file, 'file!'); equal(file, 'file!');
equal(content, 'simple\n'); equal(content, 'simple\n');
equal(log, ''); equal(log, '');
}); });
it('should output minimized templates', function() { it('should output minimized templates', function() {
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd'; uglify.minify = function() { return {code: 'min'}; };
};
uglify.minify = function() {
return { code: 'min' };
};
Precompiler.cli({templates: [emptyTemplate], min: true}); Precompiler.cli({templates: [emptyTemplate], min: true});
equal(log, 'min'); equal(log, 'min');
}); });
@@ -252,9 +191,7 @@ describe('precompiler', function() {
error.code = 'MODULE_NOT_FOUND'; error.code = 'MODULE_NOT_FOUND';
mockRequireUglify(error, function() { mockRequireUglify(error, function() {
var Precompiler = require('../dist/cjs/precompiler'); var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd';
};
Precompiler.cli({templates: [emptyTemplate], min: true}); Precompiler.cli({templates: [emptyTemplate], min: true});
equal(/template\(amd\)/.test(log), true); equal(/template\(amd\)/.test(log), true);
equal(/\n/.test(log), true); equal(/\n/.test(log), true);
@@ -264,17 +201,11 @@ describe('precompiler', function() {
it('should fail on errors (other than missing module) while loading uglify-js', function() { it('should fail on errors (other than missing module) while loading uglify-js', function() {
mockRequireUglify(new Error('Mock Error'), function() { mockRequireUglify(new Error('Mock Error'), function() {
shouldThrow( shouldThrow(function() {
function() {
var Precompiler = require('../dist/cjs/precompiler'); var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() { Handlebars.precompile = function() { return 'amd'; };
return 'amd';
};
Precompiler.cli({templates: [emptyTemplate], min: true}); Precompiler.cli({templates: [emptyTemplate], min: true});
}, }, Error, 'Mock Error');
Error,
'Mock Error'
);
}); });
}); });
@@ -286,11 +217,7 @@ describe('precompiler', function() {
}); });
it('should output map', function() { it('should output map', function() {
Precompiler.cli({ Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'});
templates: [emptyTemplate],
min: true,
map: 'foo.js.map'
});
equal(file, 'foo.js.map'); equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1); equal(log.match(/sourceMappingURL=/g).length, 1);
@@ -304,42 +231,29 @@ describe('precompiler', function() {
}); });
}); });
it('should enumerate directories by extension', function(done) { it('should enumerate directories by extension', function(done) {
Precompiler.loadTemplates( Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hbs'}, function(err, opts) {
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
function(err, opts) {
equal(opts.templates.length, 1); equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2'); equal(opts.templates[0].name, 'example_2');
done(err); done(err);
} });
);
}); });
it('should enumerate all templates by extension', function(done) { it('should enumerate all templates by extension', function(done) {
Precompiler.loadTemplates( Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'handlebars'}, function(err, opts) {
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
function(err, opts) {
equal(opts.templates.length, 3); equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom'); equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty'); equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1'); equal(opts.templates[2].name, 'example_1');
done(err); done(err);
} });
);
}); });
it('should handle regular expression characters in extensions', function(done) { it('should handle regular expression characters in extensions', function(done) {
Precompiler.loadTemplates( Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hb(s'}, function(err) {
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
function(err) {
// Success is not throwing // Success is not throwing
done(err); done(err);
} });
);
}); });
it('should handle BOM', function(done) { it('should handle BOM', function(done) {
var opts = { var opts = {files: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true};
files: [__dirname + '/artifacts/bom.handlebars'],
extension: 'handlebars',
bom: true
};
Precompiler.loadTemplates(opts, function(err, opts) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].source, 'a'); equal(opts.templates[0].source, 'a');
done(err); done(err);
@@ -347,11 +261,7 @@ describe('precompiler', function() {
}); });
it('should handle different root', function(done) { it('should handle different root', function(done) {
var opts = { var opts = {files: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'};
files: [__dirname + '/artifacts/empty.handlebars'],
simple: true,
root: 'foo/'
};
Precompiler.loadTemplates(opts, function(err, opts) { Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, __dirname + '/artifacts/empty'); equal(opts.templates[0].name, __dirname + '/artifacts/empty');
done(err); done(err);
@@ -389,10 +299,7 @@ describe('precompiler', function() {
it('error on name missing', function(done) { it('error on name missing', function(done) {
var opts = {string: ['', 'bar']}; var opts = {string: ['', 'bar']};
Precompiler.loadTemplates(opts, function(err) { Precompiler.loadTemplates(opts, function(err) {
equal( equal(err.message, 'Number of names did not match the number of string inputs');
err.message,
'Number of names did not match the number of string inputs'
);
done(); done();
}); });
}); });
+96 -280
View File
@@ -1,78 +1,50 @@
describe('Regressions', function() { describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() { it('GH-94: Cannot read property of undefined', function() {
var data = { var data = {
books: [ 'books': [{
{ 'title': 'The origin of species',
title: 'The origin of species', 'author': {
author: { 'name': 'Charles Darwin'
name: 'Charles Darwin'
} }
}, }, {
{ 'title': 'Lazarillo de Tormes'
title: 'Lazarillo de Tormes' }]
}
]
}; };
var string = '{{#books}}{{title}}{{author.name}}{{/books}}'; var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
shouldCompileTo( shouldCompileTo(string, data, 'The origin of speciesCharles DarwinLazarillo de Tormes',
string, 'Renders without an undefined property error');
data,
'The origin of speciesCharles DarwinLazarillo de Tormes',
'Renders without an undefined property error'
);
}); });
it("GH-150: Inverted sections print when they shouldn't", function() { it("GH-150: Inverted sections print when they shouldn't", function() {
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
shouldCompileTo( shouldCompileTo(string, {}, 'not set :: ', "inverted sections run when property isn't present in context");
string, 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');
'not set :: ', shouldCompileTo(string, {set: true}, ' :: set', "inverted sections don't run when property is true");
"inverted sections run when property isn't present in context"
);
shouldCompileTo(
string,
{ set: undefined },
'not set :: ',
'inverted sections run when property is undefined'
);
shouldCompileTo(
string,
{ set: false },
'not set :: ',
'inverted sections run when property is false'
);
shouldCompileTo(
string,
{ set: true },
' :: set',
"inverted sections don't run when property is true"
);
}); });
it('GH-158: Using array index twice, breaks the template', function() { it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}'; var string = '{{arr.[0]}}, {{arr.[1]}}';
var data = { arr: [1, 2] }; var data = { 'arr': [1, 2] };
shouldCompileTo(string, data, '1, 2', 'it works as expected'); shouldCompileTo(string, data, '1, 2', 'it works as expected');
}); });
it("bug reported by @fat where lambdas weren't being properly resolved", function() { it("bug reported by @fat where lambdas weren't being properly resolved", function() {
var string = var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
'<strong>This is a slightly more complicated {{thing}}.</strong>.\n' + + '{{! Just ignore this business. }}\n'
'{{! Just ignore this business. }}\n' + + 'Check this out:\n'
'Check this out:\n' + + '{{#hasThings}}\n'
'{{#hasThings}}\n' + + '<ul>\n'
'<ul>\n' + + '{{#things}}\n'
'{{#things}}\n' + + '<li class={{className}}>{{word}}</li>\n'
'<li class={{className}}>{{word}}</li>\n' + + '{{/things}}</ul>.\n'
'{{/things}}</ul>.\n' + + '{{/hasThings}}\n'
'{{/hasThings}}\n' + + '{{^hasThings}}\n'
'{{^hasThings}}\n' + + '\n'
'\n' + + '<small>Nothing to check out...</small>\n'
'<small>Nothing to check out...</small>\n' + + '{{/hasThings}}';
'{{/hasThings}}';
var data = { var data = {
thing: function() { thing: function() {
return 'blah'; return 'blah';
@@ -87,14 +59,13 @@ describe('Regressions', function() {
} }
}; };
var output = var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
'<strong>This is a slightly more complicated blah.</strong>.\n' + + 'Check this out:\n'
'Check this out:\n' + + '<ul>\n'
'<ul>\n' + + '<li class=one>@fat</li>\n'
'<li class=one>@fat</li>\n' + + '<li class=two>@dhg</li>\n'
'<li class=two>@dhg</li>\n' + + '<li class=three>@sayrer</li>\n'
'<li class=three>@sayrer</li>\n' + + '</ul>.\n';
'</ul>.\n';
shouldCompileTo(string, data, output); shouldCompileTo(string, data, output);
}); });
@@ -104,31 +75,19 @@ describe('Regressions', function() {
{ name: 'Jane Doe', location: { city: 'New York'} } { name: 'Jane Doe', location: { city: 'New York'} }
]; ];
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
);
var result = template(context); var result = template(context);
equals( equals(result, 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', 'It should output multiple times');
result,
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
'It should output multiple times'
);
}); });
it('GS-428: Nested if else rendering', function() { it('GS-428: Nested if else rendering', function() {
var succeedingTemplate = var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = { var helpers = {
blk: function(block) { blk: function(block) { return block.fn(''); },
return block.fn(''); inverse: function(block) { return block.inverse(''); }
},
inverse: function(block) {
return block.inverse('');
}
}; };
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
@@ -145,11 +104,11 @@ describe('Regressions', function() {
it('GH-534: Object prototype aliases', function() { it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */ /* eslint-disable no-extend-native */
Object.prototype[0xd834] = true; Object.prototype[0xD834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xd834]; delete Object.prototype[0xD834];
/* eslint-enable no-extend-native */ /* eslint-enable no-extend-native */
}); });
@@ -164,35 +123,25 @@ describe('Regressions', function() {
it('GH-676: Using array in escaping mustache fails', function() { it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}'; var string = '{{arr}}';
var data = { arr: [1, 2] }; var data = { 'arr': [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected'); shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
}); });
it('Mustache man page', function() { it('Mustache man page', function() {
var string = var string = 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
var data = { var data = {
name: 'Chris', 'name': 'Chris',
value: 10000, 'value': 10000,
taxed_value: 10000 - 10000 * 0.4, 'taxed_value': 10000 - (10000 * 0.4),
in_ca: true 'in_ca': true
}; };
shouldCompileTo( shouldCompileTo(string, data, 'Hello Chris. You have just won $10000! Well, $6000, after taxes.', 'the hello world mustache example works');
string,
data,
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
'the hello world mustache example works'
);
}); });
it('GH-731: zero context rendering', function() { it('GH-731: zero context rendering', function() {
shouldCompileTo( shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is ~ ');
'{{#foo}} This is {{bar}} ~ {{/foo}}',
{ foo: 0, bar: 'OK' },
' This is ~ '
);
}); });
it('GH-820: zero pathed rendering', function() { it('GH-820: zero pathed rendering', function() {
@@ -201,9 +150,7 @@ describe('Regressions', function() {
it('GH-837: undefined values for helpers', function() { it('GH-837: undefined values for helpers', function() {
var helpers = { var helpers = {
str: function(value) { str: function(value) { return value + ''; }
return value + '';
}
}; };
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined'); shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
@@ -212,13 +159,15 @@ describe('Regressions', function() {
it('GH-926: Depths and de-dupe', function() { it('GH-926: Depths and de-dupe', function() {
var context = { var context = {
name: 'foo', name: 'foo',
data: [1], data: [
notData: [1] 1
],
notData: [
1
]
}; };
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}');
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
);
var result = template(context); var result = template(context);
equals(result, 'foo'); equals(result, 'foo');
@@ -227,15 +176,11 @@ describe('Regressions', function() {
it('GH-1021: Each empty string key', function() { it('GH-1021: Each empty string key', function() {
var data = { var data = {
'': 'foo', '': 'foo',
name: 'Chris', 'name': 'Chris',
value: 10000 'value': 10000
}; };
shouldCompileTo( shouldCompileTo('{{#each data}}Key: {{@key}}\n{{/each}}', {data: data}, 'Key: \nKey: name\nKey: value\n');
'{{#each data}}Key: {{@key}}\n{{/each}}',
{ data: data },
'Key: \nKey: name\nKey: value\n'
);
}); });
it('GH-1054: Should handle simple safe string responses', function() { it('GH-1054: Should handle simple safe string responses', function() {
@@ -249,23 +194,14 @@ describe('Regressions', function() {
} }
}; };
shouldCompileToWithPartials( shouldCompileToWithPartials(root, [{}, helpers, partials], true, '<partial>');
root,
[{}, helpers, partials],
true,
'<partial>'
);
}); });
it('GH-1065: Sparse arrays', function() { it('GH-1065: Sparse arrays', function() {
var array = []; var array = [];
array[1] = 'foo'; array[1] = 'foo';
array[3] = 'bar'; array[3] = 'bar';
shouldCompileTo( shouldCompileTo('{{#each array}}{{@index}}{{.}}{{/each}}', {array: array}, '1foo3bar');
'{{#each array}}{{@index}}{{.}}{{/each}}',
{ array: array },
'1foo3bar'
);
}); });
it('GH-1093: Undefined helper context', function() { it('GH-1093: Undefined helper context', function() {
@@ -280,45 +216,28 @@ describe('Regressions', function() {
} }
} }
// And to make IE happy, check for the known string as length is not enumerated. // And to make IE happy, check for the known string as length is not enumerated.
return this === 'bat' ? 'found' : 'not'; return (this === 'bat' ? 'found' : 'not');
} }
}; };
shouldCompileTo( shouldCompileTo('{{#each obj}}{{{helper}}}{{.}}{{/each}}', [{obj: obj}, helpers], 'notfoundbat');
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
[{ obj: obj }, helpers],
'notfoundbat'
);
}); });
it('should support multiple levels of inline partials', function() { it('should support multiple levels of inline partials', function() {
var string = var string = '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var partials = { var partials = {
doctype: 'doctype{{> content}}', doctype: 'doctype{{> content}}',
layout: layout: '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
}; };
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
}); });
it('GH-1089: should support failover content in multiple levels of inline partials', function() { it('GH-1089: should support failover content in multiple levels of inline partials', function() {
var string = '{{#> layout}}{{/layout}}'; var string = '{{#> layout}}{{/layout}}';
var partials = { var partials = {
doctype: 'doctype{{> content}}', doctype: 'doctype{{> content}}',
layout: layout: '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
}; };
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
}); });
it('GH-1099: should support greater than 3 nested levels of inline partials', function() { it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}'; var string = '{{#> layout}}Outer{{/layout}}';
@@ -342,142 +261,66 @@ describe('Regressions', function() {
}; };
shouldCompileTo( shouldCompileTo(
'{{#each array}}\n' + '{{#each array}}\n'
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' + + ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n'
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' + + ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n'
'{{/each}}', + '{{/each}}', [obj, helpers],
[obj, helpers], ' 1. IF: John--\n'
' 1. IF: John--\n' + ' 2. MYIF: John==\n' + ' 2. MYIF: John==\n');
);
}); });
it('GH-1186: Support block params for existing programs', function() { it('GH-1186: Support block params for existing programs', function() {
var string = var string =
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + '{{#*inline "test"}}{{> @partial-block }}{{/inline}}'
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + + '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}'
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'; + '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', ''); shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', '');
}); });
it('GH-1319: "unless" breaks when "each" value equals "null"', function() { it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
var string = var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', '');
shouldCompileTo(
string,
{ value: 'parent', list: [null, 'a'] },
'parent=parent parent=parent ',
''
);
}); });
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
var string = 'template {{>partial}} template'; var string = 'template {{>partial}} template';
var partials = { var partials = {
partialWithBlock: partialWithBlock: '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
}; };
shouldCompileToWithPartials( shouldCompileToWithPartials(string, [{}, {}, partials], true, 'template block partial block template');
string,
[{}, {}, partials],
true,
'template block partial block template'
);
}); });
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() { describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
it('should compile and execute templates', function() { it('should compile and execute templates', function() {
var newHandlebarsInstance = Handlebars.create(); var newHandlebarsInstance = Handlebars.create();
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7()); registerTemplate(newHandlebarsInstance);
newHandlebarsInstance.registerHelper('loud', function(value) { newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase(); return value.toUpperCase();
}); });
var result = newHandlebarsInstance.templates['test.hbs']({ var result = newHandlebarsInstance.templates['test.hbs']({name: 'yehuda'});
name: 'yehuda'
});
equals(result.trim(), 'YEHUDA'); equals(result.trim(), 'YEHUDA');
}); });
it('should call "helperMissing" if a helper is missing', function() { it('should call "helperMissing" if a helper is missing', function() {
var newHandlebarsInstance = Handlebars.create(); var newHandlebarsInstance = Handlebars.create();
shouldThrow( shouldThrow(function() {
function() { registerTemplate(newHandlebarsInstance);
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
newHandlebarsInstance.templates['test.hbs']({}); newHandlebarsInstance.templates['test.hbs']({});
}, }, Handlebars.Exception, 'Missing helper: "loud"');
Handlebars.Exception,
'Missing helper: "loud"'
);
}); });
it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function() { // This is a only slightly modified precompiled templated from compiled with 4.2.1
var newHandlebarsInstance = Handlebars.create(); function registerTemplate(Handlebars) {
registerTemplate( var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
newHandlebarsInstance, templates['test.hbs'] = template({'compiler': [7, '>= 4.0.0'], 'main': function(container, depth0, helpers, partials, data) {
compiledTemplateVersion7_usingLookupHelper() 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});
newHandlebarsInstance.templates['test.hbs']({});
expect(
newHandlebarsInstance.templates['test.hbs']({
property: 'a',
test: { a: 'b' }
})
).to.equal('b');
});
function registerTemplate(Handlebars, compileTemplate) {
var template = Handlebars.template,
templates = (Handlebars.templates = Handlebars.templates || {});
templates['test.hbs'] = template(compileTemplate);
}
function compiledTemplateVersion7() {
return {
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
};
}
function compiledTemplateVersion7_usingLookupHelper() {
// This is the compiled version of "{{lookup test property}}"
return {
compiler: [7, '>= 4.0.0'],
main: function(container, depth0, helpers, partials, data) {
return container.escapeExpression(
helpers.lookup.call(
depth0 != null ? depth0 : container.nullContext || {},
depth0 != null ? depth0.test : depth0,
depth0 != null ? depth0.property : depth0,
{
name: 'lookup',
hash: {},
data: data
}
)
);
},
useData: true
};
} }
}); });
@@ -491,31 +334,4 @@ describe('Regressions', function() {
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo'); shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
}); });
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
// Do not run test for runs without compiler
if (!Handlebars.compile) {
return;
}
var newHandlebarsInstance;
beforeEach(function() {
newHandlebarsInstance = Handlebars.create();
});
afterEach(function() {
sinon.restore();
});
it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({
dude: 'I am a partial'
});
var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
newHandlebarsInstance.compile(string)(); // This should only compile template
equal(templateSpy.callCount, 3);
sinon.restore();
});
});
}); });
+16 -48
View File
@@ -1,58 +1,34 @@
describe('runtime', function() { describe('runtime', function() {
describe('#template', function() { describe('#template', function() {
it('should throw on invalid templates', function() { it('should throw on invalid templates', function() {
shouldThrow( shouldThrow(function() {
function() {
Handlebars.template({}); Handlebars.template({});
}, }, Error, 'Unknown template object: object');
Error, shouldThrow(function() {
'Unknown template object: object'
);
shouldThrow(
function() {
Handlebars.template(); Handlebars.template();
}, }, Error, 'Unknown template object: undefined');
Error, shouldThrow(function() {
'Unknown template object: undefined'
);
shouldThrow(
function() {
Handlebars.template(''); Handlebars.template('');
}, }, Error, 'Unknown template object: string');
Error,
'Unknown template object: string'
);
}); });
it('should throw on version mismatch', function() { it('should throw on version mismatch', function() {
shouldThrow( shouldThrow(function() {
function() {
Handlebars.template({ Handlebars.template({
main: {}, main: {},
compiler: [Handlebars.COMPILER_REVISION + 1] compiler: [Handlebars.COMPILER_REVISION + 1]
}); });
}, }, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
Error, shouldThrow(function() {
/Template was precompiled with a newer version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({ Handlebars.template({
main: {}, main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1] compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
}); });
}, }, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
Error, shouldThrow(function() {
/Template was precompiled with an older version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({ Handlebars.template({
main: {} main: {}
}); });
}, }, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
}); });
}); });
@@ -62,31 +38,23 @@ describe('runtime', function() {
} }
it('should throw for depthed methods without depths', function() { it('should throw for depthed methods without depths', function() {
shouldThrow( shouldThrow(function() {
function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}'); var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case. // Calling twice to hit the non-compiled case.
template._setup({}); template._setup({});
template._setup({}); template._setup({});
template._child(1); template._child(1);
}, }, Error, 'must pass parent depths');
Error,
'must pass parent depths'
);
}); });
it('should throw for block param methods without params', function() { it('should throw for block param methods without params', function() {
shouldThrow( shouldThrow(function() {
function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}'); var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case. // Calling twice to hit the non-compiled case.
template._setup({}); template._setup({});
template._setup({}); template._setup({});
template._child(1); template._child(1);
}, }, Error, 'must pass block params');
Error,
'must pass block params'
);
}); });
it('should expose child template', function() { it('should expose child template', function() {
var template = Handlebars.compile('{{#foo}}bar{{/foo}}'); var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
+52 -315
View File
@@ -19,37 +19,39 @@ describe('security issues', function() {
.toCompileTo(''); .toCompileTo('');
}); });
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
shouldCompileTo( it('should allow the "constructor" property to be accessed if it is enumerable', function() {
'{{constructor.name}}', shouldCompileTo('{{constructor.name}}', {'constructor': {
{ 'name': 'here we go'
constructor: { }}, 'here we go');
name: 'here we go' shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
} 'name': 'here we go'
}, }}, 'here we go');
'here we go'
);
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
}); });
it('should allow the "constructor" property to be accessed if it is an "own property"', function() { it('should allow the "constructor" property to be accessed if it is enumerable', function() {
shouldCompileTo( shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': {
'{{lookup (lookup this "constructor") "name"}}', 'name': 'here we go'
{ }}, 'here we go');
constructor: { });
name: 'here we go'
it('should allow prototype properties that are not constructors', function() {
function TestClass() {
} }
},
'here we go' 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');
}); });
}); });
@@ -67,9 +69,7 @@ describe('security issues', function() {
}); });
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() { shouldThrow(function() {
var template = Handlebars.compile( var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
'{{#helperMissing}}{{/helperMissing}}'
);
template({}); template({});
}, Error); }, Error);
}); });
@@ -77,24 +77,14 @@ describe('security issues', function() {
var functionCalls = []; var functionCalls = [];
expect(function() { expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({ template({ fn: function() { functionCalls.push('called'); }});
fn: function() {
functionCalls.push('called');
}
});
}).to.throw(Error); }).to.throw(Error);
expect(functionCalls.length).to.equal(0); expect(functionCalls.length).to.equal(0);
}); });
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() { shouldThrow(function() {
var template = Handlebars.compile( var template = Handlebars.compile('{{#blockHelperMissing .}}{{/blockHelperMissing}}');
'{{#blockHelperMissing .}}{{/blockHelperMissing}}' template({ fn: function() { return 'functionInData';}});
);
template({
fn: function() {
return 'functionInData';
}
});
}, Error); }, Error);
}); });
}); });
@@ -105,28 +95,17 @@ describe('security issues', function() {
template({}, {allowCallsToHelperMissing: true}); template({}, {allowCallsToHelperMissing: true});
}); });
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile( var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
'{{#helperMissing}}{{/helperMissing}}'
);
template({}, {allowCallsToHelperMissing: true}); template({}, {allowCallsToHelperMissing: true});
}); });
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = []; var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template( template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true});
{
fn: function() {
functionCalls.push('called');
}
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1); equals(functionCalls.length, 1);
}); });
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile( var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}');
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, {allowCallsToHelperMissing: true}); template({}, {allowCallsToHelperMissing: true});
}); });
}); });
@@ -134,274 +113,32 @@ describe('security issues', function() {
describe('GH-1563', function() { describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() { it('should not allow to access constructor after overriding via __defineGetter__', function() {
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) { if (({}).__defineGetter__ == null || ({}).__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway return this.skip(); // Browser does not support this exploit anyway
} }
expectTemplate( expectTemplate('{{__defineGetter__ "undefined" valueOf }}' +
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' + '{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' + '{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' + '{{constructor.name}}' +
'{{/with}}' '{{/with}}')
)
.withInput({}) .withInput({})
.toThrow(/Missing helper: "__defineGetter__"/); .toThrow(/Missing helper: "__defineGetter__"/);
}); });
}); });
describe('GH-1595: dangerous properties', function() { describe('GH-1595', function() {
var templates = [ it('properties, that are required to be enumerable', function() {
'{{constructor}}', expectTemplate('{{constructor}}').withInput({}).toCompileTo('');
'{{__defineGetter__}}', expectTemplate('{{__defineGetter__}}').withInput({}).toCompileTo('');
'{{__defineSetter__}}', expectTemplate('{{__defineSetter__}}').withInput({}).toCompileTo('');
'{{__lookupGetter__}}', expectTemplate('{{__lookupGetter__}}').withInput({}).toCompileTo('');
'{{__proto__}}', expectTemplate('{{__proto__}}').withInput({}).toCompileTo('');
'{{lookup this "constructor"}}',
'{{lookup this "__defineGetter__"}}',
'{{lookup this "__defineSetter__"}}',
'{{lookup this "__lookupGetter__"}}',
'{{lookup this "__proto__"}}'
];
templates.forEach(function(template) { expectTemplate('{{lookup "constructor"}}').withInput({}).toCompileTo('');
describe('access should be denied to ' + template, function() { expectTemplate('{{lookup "__defineGetter__"}}').withInput({}).toCompileTo('');
it('by default', function() { expectTemplate('{{lookup "__defineSetter__"}}').withInput({}).toCompileTo('');
expectTemplate(template) expectTemplate('{{lookup "__lookupGetter__"}}').withInput({}).toCompileTo('');
.withInput({}) expectTemplate('{{lookup "__proto__"}}').withInput({}).toCompileTo('');
.toCompileTo('');
});
it(' with proto-access enabled', function() {
expectTemplate(template)
.withInput({})
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowProtoMethodsByDefault: true
})
.toCompileTo('');
}); });
}); });
}); });
});
describe('GH-1631: disallow access to prototype functions', function() {
function TestClass() {}
TestClass.prototype.aProperty = 'propertyValue';
TestClass.prototype.aMethod = function() {
return 'returnValue';
};
afterEach(function() {
sinon.restore();
});
describe('control access to prototype methods via "allowedProtoMethods"', function() {
checkProtoMethodAccess({});
describe('in compat mode', function() {
checkProtoMethodAccess({ compat: true });
});
function checkProtoMethodAccess(compileOptions) {
it('should be prohibited by default and log a warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be allowed, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoMethods: {
aMethod: true
}
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: false
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function() {
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true,
allowedProtoMethods: {
aMethod: false
}
})
.toCompileTo('');
});
}
it('should cause the recursive lookup by default (in "compat" mode)', function() {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.toCompileTo('trim');
});
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.withRuntimeOptions({
allowedProtoMethods: {
trim: true
}
})
.toCompileTo('abc');
});
});
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() {
checkProtoPropertyAccess({});
describe('in compat-mode', function() {
checkProtoPropertyAccess({ compat: true });
});
function checkProtoPropertyAccess(compileOptions) {
it('should be prohibited by default and log a warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be explicitly prohibited by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: false
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned on, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoProperties: {
aProperty: true
}
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function() {
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowedProtoProperties: {
aProperty: false
}
})
.toCompileTo('');
});
}
});
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() {
beforeEach(function simulateRuntimeWithoutLookupProperty() {
var oldTemplateMethod = handlebarsEnv.template;
sinon.replace(handlebarsEnv, 'template', function(templateSpec) {
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
return oldTemplateMethod.call(this, templateSpec);
});
});
afterEach(function() {
sinon.restore();
});
it('should work with simple properties', function() {
expectTemplate('{{aProperty}}')
.withInput({ aProperty: 'propertyValue' })
.toCompileTo('propertyValue');
});
it('should work with Array.prototype.length', function() {
expectTemplate('{{anArray.length}}')
.withInput({ anArray: ['a', 'b', 'c'] })
.toCompileTo('3');
});
});
});
});
function wrapToAdjustContainer(precompiledTemplateFunction) {
return function templateFunctionWrapper(container /*, more args */) {
delete container.lookupProperty;
return precompiledTemplateFunction.apply(this, arguments);
};
}
+4 -10
View File
@@ -13,26 +13,20 @@ describe('source-map', function() {
} }
it('should safely include source map info', function() { it('should safely include source map info', function() {
var template = Handlebars.precompile('{{hello}}', { var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
destName: 'dest.js',
srcName: 'src.hbs'
});
equal(!!template.code, true); equal(!!template.code, true);
equal(!!template.map, !CompilerContext.browser); equal(!!template.map, !CompilerContext.browser);
}); });
it('should map source properly', function() { it('should map source properly', function() {
var templateSource = var templateSource = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}', template = Handlebars.precompile(templateSource, {destName: 'dest.js', srcName: 'src.hbs'});
template = Handlebars.precompile(templateSource, {
destName: 'dest.js',
srcName: 'src.hbs'
});
if (template.map) { if (template.map) {
var consumer = new SourceMapConsumer(template.map), var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'), lines = template.code.split('\n'),
srcLines = templateSource.split('\n'), srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines), generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines); source = grepLine(' b', srcLines);
+11 -23
View File
@@ -9,7 +9,7 @@ describe('spec', function() {
var specDir = __dirname + '/mustache/specs/'; var specDir = __dirname + '/mustache/specs/';
var specs = _.filter(fs.readdirSync(specDir), function(name) { var specs = _.filter(fs.readdirSync(specDir), function(name) {
return /.*\.json$/.test(name); return (/.*\.json$/).test(name);
}); });
_.each(specs, function(name) { _.each(specs, function(name) {
@@ -17,17 +17,16 @@ describe('spec', function() {
_.each(spec.tests, function(test) { _.each(spec.tests, function(test) {
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec // Our lambda implementation knowingly deviates from the optional Mustace lambda spec
// We also do not support alternative delimeters // We also do not support alternative delimeters
if ( if (name === '~lambdas.json'
name === '~lambdas.json' ||
// We also choose to throw if paritals are not found // We also choose to throw if paritals are not found
(name === 'partials.json' && test.name === 'Failed Lookup') || || (name === 'partials.json' && test.name === 'Failed Lookup')
// We nest the entire response from partials, not just the literals // We nest the entire response from partials, not just the literals
(name === 'partials.json' && test.name === 'Standalone Indentation') || || (name === 'partials.json' && test.name === 'Standalone Indentation')
/\{\{=/.test(test.template) ||
_.any(test.partials, function(partial) { || (/\{\{=/).test(test.template)
return /\{\{=/.test(partial); || _.any(test.partials, function(partial) { return (/\{\{=/).test(partial); })) {
})
) {
it.skip(name + ' - ' + test.name); it.skip(name + ' - ' + test.name);
return; return;
} }
@@ -41,20 +40,9 @@ describe('spec', function() {
} }
it(name + ' - ' + test.name, function() { it(name + ' - ' + test.name, function() {
if (test.partials) { if (test.partials) {
shouldCompileToWithPartials( shouldCompileToWithPartials(test.template, [data, {}, test.partials, true], true, test.expected, test.desc + ' "' + test.template + '"');
test.template,
[data, {}, test.partials, true],
true,
test.expected,
test.desc + ' "' + test.template + '"'
);
} else { } else {
shouldCompileTo( shouldCompileTo(test.template, [data, {}, {}, true], test.expected, test.desc + ' "' + test.template + '"');
test.template,
[data, {}, {}, true],
test.expected,
test.desc + ' "' + test.template + '"'
);
} }
}); });
}); });
+34 -105
View File
@@ -3,27 +3,19 @@ var Exception = Handlebars.Exception;
describe('strict', function() { describe('strict', function() {
describe('strict mode', function() { describe('strict mode', function() {
it('should error on missing property lookup', function() { it('should error on missing property lookup', function() {
shouldThrow( shouldThrow(function() {
function() {
var template = CompilerContext.compile('{{hello}}', {strict: true}); var template = CompilerContext.compile('{{hello}}', {strict: true});
template({}); template({});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
}); });
it('should error on missing child', function() { it('should error on missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true}); var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
equals(template({hello: {bar: 'foo'}}), 'foo'); equals(template({hello: {bar: 'foo'}}), 'foo');
shouldThrow( shouldThrow(function() {
function() {
template({hello: {}}); template({hello: {}});
}, }, Exception, /"bar" not defined in/);
Exception,
/"bar" not defined in/
);
}); });
it('should handle explicit undefined', function() { it('should handle explicit undefined', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true}); var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
@@ -31,18 +23,11 @@ describe('strict', function() {
equals(template({hello: {bar: undefined}}), ''); equals(template({hello: {bar: undefined}}), '');
}); });
it('should error on missing property lookup in known helpers mode', function() { it('should error on missing property lookup in known helpers mode', function() {
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true});
var template = CompilerContext.compile('{{hello}}', {
strict: true,
knownHelpersOnly: true
});
template({}); template({});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
}); });
it('should error on missing context', function() { it('should error on missing context', function() {
shouldThrow(function() { shouldThrow(function() {
@@ -62,81 +47,45 @@ describe('strict', function() {
}); });
it('should not run helperMissing for helper calls', function() { it('should not run helperMissing for helper calls', function() {
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{hello foo}}', {strict: true});
var template = CompilerContext.compile('{{hello foo}}', {
strict: true
});
template({foo: true}); template({foo: true});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true});
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
strict: true
});
template({foo: true}); template({foo: true});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
}); });
it('should throw on ambiguous blocks', function() { it('should throw on ambiguous blocks', function() {
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true});
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
strict: true
});
template({}); template({});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true});
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
strict: true
});
template({}); template({});
}, }, Exception, /"hello" not defined in/);
Exception,
/"hello" not defined in/
);
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true});
var template = CompilerContext.compile(
'{{#hello.bar}}{{/hello.bar}}',
{ strict: true }
);
template({hello: {}}); template({hello: {}});
}, }, Exception, /"bar" not defined in/);
Exception,
/"bar" not defined in/
);
}); });
it('should allow undefined parameters when passed to helpers', function() { it('should allow undefined parameters when passed to helpers', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#unless foo}}success{{/unless}}', {strict: true});
'{{#unless foo}}success{{/unless}}',
{ strict: true }
);
equals(template({}), 'success'); equals(template({}), 'success');
}); });
it('should allow undefined hash when passed to helpers', function() { it('should allow undefined hash when passed to helpers', function() {
var template = CompilerContext.compile('{{helper value=@foo}}', { var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
strict: true
});
var helpers = { var helpers = {
helper: function(options) { helper: function(options) {
equals('value' in options.hash, true); equals('value' in options.hash, true);
@@ -148,23 +97,15 @@ describe('strict', function() {
}); });
it('should show error location on missing property lookup', function() { it('should show error location on missing property lookup', function() {
shouldThrow( shouldThrow(function() {
function() { var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({}); template({});
}, }, Exception, '"hello" not defined in [object Object] - 4:5');
Exception,
'"hello" not defined in [object Object] - 4:5'
);
}); });
it('should error contains correct location properties on missing property lookup', function() { it('should error contains correct location properties on missing property lookup', function() {
try { try {
var template = CompilerContext.compile('\n\n\n {{hello}}', { var template = CompilerContext.compile('\n\n\n {{hello}}', {strict: true});
strict: true
});
template({}); template({});
} catch (error) { } catch (error) {
equals(error.lineNumber, 4); equals(error.lineNumber, 4);
@@ -177,33 +118,25 @@ describe('strict', function() {
describe('assume objects', function() { describe('assume objects', function() {
it('should ignore missing property', function() { it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', { var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
assumeObjects: true
});
equal(template({}), ''); equal(template({}), '');
}); });
it('should ignore missing child', function() { it('should ignore missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', { var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
assumeObjects: true
});
equal(template({hello: {}}), ''); equal(template({hello: {}}), '');
}); });
it('should error on missing object', function() { it('should error on missing object', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', { var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
assumeObjects: true
});
template({}); template({});
}, Error); }, Error);
}); });
it('should error on missing context', function() { it('should error on missing context', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', { var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
assumeObjects: true
});
template(); template();
}, Error); }, Error);
@@ -211,18 +144,14 @@ describe('strict', function() {
it('should error on missing data lookup', function() { it('should error on missing data lookup', function() {
shouldThrow(function() { shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', { var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true});
assumeObjects: true
});
template(); template();
}, Error); }, Error);
}); });
it('should execute blockHelperMissing', function() { it('should execute blockHelperMissing', function() {
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', { var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {assumeObjects: true});
assumeObjects: true
});
equals(template({}), 'foo'); equals(template({}), 'foo');
}); });
+30 -100
View File
@@ -1,8 +1,6 @@
describe('string params mode', function() { describe('string params mode', function() {
it('arguments to helpers can be retrieved from options hash in string form', function() { it('arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', { var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
stringParams: true
});
var helpers = { var helpers = {
wycats: function(passiveVoice, noun) { wycats: function(passiveVoice, noun) {
@@ -12,97 +10,55 @@ describe('string params mode', function() {
var result = template({}, {helpers: helpers}); var result = template({}, {helpers: helpers});
equals( equals(result, 'HELP ME MY BOSS is.a slave.driver', 'String parameters output');
result,
'HELP ME MY BOSS is.a slave.driver',
'String parameters output'
);
}); });
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() { it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
'{{#wycats is.a slave.driver}}help :({{/wycats}}',
{ stringParams: true }
);
var helpers = { var helpers = {
wycats: function(passiveVoice, noun, options) { wycats: function(passiveVoice, noun, options) {
return ( return 'HELP ME MY BOSS ' + passiveVoice + ' ' +
'HELP ME MY BOSS ' + noun + ': ' + options.fn(this);
passiveVoice +
' ' +
noun +
': ' +
options.fn(this)
);
} }
}; };
var result = template({}, {helpers: helpers}); var result = template({}, {helpers: helpers});
equals( equals(result, 'HELP ME MY BOSS is.a slave.driver: help :(', 'String parameters output');
result,
'HELP ME MY BOSS is.a slave.driver: help :(',
'String parameters output'
);
}); });
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() { it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
'{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
{ stringParams: true }
);
var helpers = { var helpers = {
tomdale: function(desire, noun, options) { tomdale: function(desire, noun, options) {
return ( return 'STOP ME FROM READING HACKER NEWS I ' +
'STOP ME FROM READING HACKER NEWS I ' + options.contexts[0][desire] + ' ' + noun;
options.contexts[0][desire] +
' ' +
noun
);
}, },
with: function(context, options) { 'with': function(context, options) {
return options.fn(options.contexts[0][context]); return options.fn(options.contexts[0][context]);
} }
}; };
var result = template( var result = template({
{
dale: {}, dale: {},
need: 'need-a' need: 'need-a'
}, }, {helpers: helpers});
{ helpers: helpers }
);
equals( equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', 'Proper context variable output');
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
'Proper context variable output'
);
}); });
it('information about the types is passed along', function() { it('information about the types is passed along', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile("{{tomdale 'need' dad.joke true false}}", { stringParams: true });
"{{tomdale 'need' dad.joke true false}}",
{ stringParams: true }
);
var helpers = { var helpers = {
tomdale: function(desire, noun, trueBool, falseBool, options) { tomdale: function(desire, noun, trueBool, falseBool, options) {
equal(options.types[0], 'StringLiteral', 'the string type is passed'); equal(options.types[0], 'StringLiteral', 'the string type is passed');
equal( equal(options.types[1], 'PathExpression', 'the expression type is passed');
options.types[1], equal(options.types[2], 'BooleanLiteral', 'the expression type is passed');
'PathExpression',
'the expression type is passed'
);
equal(
options.types[2],
'BooleanLiteral',
'the expression type is passed'
);
equal(desire, 'need', 'the string form is passed for strings'); equal(desire, 'need', 'the string form is passed for strings');
equal(noun, 'dad.joke', 'the string form is passed for expressions'); equal(noun, 'dad.joke', 'the string form is passed for expressions');
equal(trueBool, true, 'raw booleans are passed through'); equal(trueBool, true, 'raw booleans are passed through');
@@ -116,10 +72,7 @@ describe('string params mode', function() {
}); });
it('hash parameters get type information', function() { it('hash parameters get type information', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile("{{tomdale he.says desire='need' noun=dad.joke bool=true}}", { stringParams: true });
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
{ stringParams: true }
);
var helpers = { var helpers = {
tomdale: function(exclamation, options) { tomdale: function(exclamation, options) {
@@ -141,10 +94,7 @@ describe('string params mode', function() {
}); });
it('hash parameters get context information', function() { it('hash parameters get context information', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile("{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}", { stringParams: true });
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
{ stringParams: true }
);
var context = {dale: {}}; var context = {dale: {}};
@@ -160,7 +110,7 @@ describe('string params mode', function() {
equal(options.hash.bool, true); equal(options.hash.bool, true);
return 'Helper called'; return 'Helper called';
}, },
with: function(withContext, options) { 'with': function(withContext, options) {
return options.fn(options.contexts[0][withContext]); return options.fn(options.contexts[0][withContext]);
} }
}; };
@@ -170,52 +120,34 @@ describe('string params mode', function() {
}); });
it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() { it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
{ stringParams: true }
);
var helpers = { var helpers = {
tomdale: function(desire, noun, options) { tomdale: function(desire, noun, options) {
return ( return 'STOP ME FROM READING HACKER NEWS I ' +
'STOP ME FROM READING HACKER NEWS I ' + options.contexts[0][desire] + ' ' + noun + ' ' +
options.contexts[0][desire] + options.fn(this);
' ' +
noun +
' ' +
options.fn(this)
);
}, },
with: function(context, options) { 'with': function(context, options) {
return options.fn(options.contexts[0][context]); return options.fn(options.contexts[0][context]);
} }
}; };
var result = template( var result = template({
{
dale: {}, dale: {},
need: 'need-a' need: 'need-a'
}, }, {helpers: helpers});
{ helpers: helpers }
);
equals( equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', 'Proper context variable output');
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
'Proper context variable output'
);
}); });
it('with nested block ambiguous', function() { it('with nested block ambiguous', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
{ stringParams: true }
);
var helpers = { var helpers = {
with: function() { 'with': function() {
return 'WITH'; return 'WITH';
}, },
view: function() { view: function() {
@@ -228,9 +160,7 @@ describe('string params mode', function() {
}); });
it('should handle DATA', function() { it('should handle DATA', function() {
var template = CompilerContext.compile('{{foo @bar}}', { var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true });
stringParams: true
});
var helpers = { var helpers = {
foo: function(bar, options) { foo: function(bar, options) {
+16 -70
View File
@@ -69,11 +69,7 @@ describe('subexpressions', function() {
} }
}; };
shouldCompileTo( shouldCompileTo("{{dash 'abc' (concat a b)}}", [context, helpers], 'abc-ab');
"{{dash 'abc' (concat a b)}}",
[context, helpers],
'abc-ab'
);
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab'); shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab'); shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c'); shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
@@ -126,36 +122,24 @@ describe('subexpressions', function() {
}); });
it('multiple subexpressions in a hash', function() { it('multiple subexpressions in a hash', function() {
var string = var string = '{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
var helpers = { var helpers = {
input: function(options) { input: function(options) {
var hash = options.hash; var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString( return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
}, },
t: function(defaultString) { t: function(defaultString) {
return new Handlebars.SafeString(defaultString); return new Handlebars.SafeString(defaultString);
} }
}; };
shouldCompileTo( shouldCompileTo(string, [{}, helpers], '<input aria-label="Name" placeholder="Example User" />');
string,
[{}, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
}); });
it('multiple subexpressions in a hash with context', function() { it('multiple subexpressions in a hash with context', function() {
var string = var string = '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var context = { var context = {
item: { item: {
@@ -169,82 +153,44 @@ describe('subexpressions', function() {
var hash = options.hash; var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString( return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
'<input aria-label="' +
ariaLabel +
'" placeholder="' +
placeholder +
'" />'
);
}, },
t: function(defaultString) { t: function(defaultString) {
return new Handlebars.SafeString(defaultString); return new Handlebars.SafeString(defaultString);
} }
}; };
shouldCompileTo( shouldCompileTo(string, [context, helpers], '<input aria-label="Name" placeholder="Example User" />');
string,
[context, helpers],
'<input aria-label="Name" placeholder="Example User" />'
);
}); });
it('in string params mode,', function() { it('in string params mode,', function() {
var template = CompilerContext.compile( var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
'{{snog (blorg foo x=y) yeah a=b}}',
{ stringParams: true }
);
var helpers = { var helpers = {
snog: function(a, b, options) { snog: function(a, b, options) {
equals(a, 'foo'); equals(a, 'foo');
equals( equals(options.types.length, 2, 'string params for outer helper processed correctly');
options.types.length, equals(options.types[0], 'SubExpression', 'string params for outer helper processed correctly');
2, equals(options.types[1], 'PathExpression', 'string params for outer helper processed correctly');
'string params for outer helper processed correctly'
);
equals(
options.types[0],
'SubExpression',
'string params for outer helper processed correctly'
);
equals(
options.types[1],
'PathExpression',
'string params for outer helper processed correctly'
);
return a + b; return a + b;
}, },
blorg: function(a, options) { blorg: function(a, options) {
equals( equals(options.types.length, 1, 'string params for inner helper processed correctly');
options.types.length, equals(options.types[0], 'PathExpression', 'string params for inner helper processed correctly');
1,
'string params for inner helper processed correctly'
);
equals(
options.types[0],
'PathExpression',
'string params for inner helper processed correctly'
);
return a; return a;
} }
}; };
var result = template( var result = template({
{
foo: {}, foo: {},
yeah: {} yeah: {}
}, }, {helpers: helpers});
{ helpers: helpers }
);
equals(result, 'fooyeah'); equals(result, 'fooyeah');
}); });
it('as hashes in string params mode', function() { it('as hashes in string params mode', function() {
var template = CompilerContext.compile('{{blog fun=(bork)}}', { var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
stringParams: true
});
var helpers = { var helpers = {
blog: function(options) { blog: function(options) {

Some files were not shown because too many files have changed in this diff Show More