Compare commits
268 Commits
v2.0.0-alpha.4
...
v3.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 891f48b7e9 | |||
| 0bf79ea5c0 | |||
| 972f521718 | |||
| 4a40fc8e2f | |||
| fc13400b6f | |||
| 6ecf509385 | |||
| d945089565 | |||
| 2f0b86665f | |||
| d48e840513 | |||
| 4bed826d0e | |||
| 0263aa48bc | |||
| ecf60ab1bf | |||
| c6ab044fa8 | |||
| fdc94207a3 | |||
| e3d3eda2e1 | |||
| 2a02261a5b | |||
| a009a97a2e | |||
| 54e743a09a | |||
| a7160a8d27 | |||
| dfd141cd35 | |||
| 37a664bf64 | |||
| c699d0b82f | |||
| 2d149e7797 | |||
| 81a4d50955 | |||
| e15af4c849 | |||
| afe730e059 | |||
| 40894e619b | |||
| 1d401a2abc | |||
| a10d907a65 | |||
| fed86c01ad | |||
| ab96073c6b | |||
| 64ab232d72 | |||
| 8c357b9243 | |||
| 71690ae39d | |||
| 3c9ff77e68 | |||
| 5ede36b8fd | |||
| c15f90e513 | |||
| 0099e16a01 | |||
| a8848d0af9 | |||
| ca6152d2f7 | |||
| 55dc82d938 | |||
| 39121cf8f5 | |||
| 07f27843dc | |||
| 5cc326d425 | |||
| f857471cc8 | |||
| fd8484798c | |||
| 810d86d474 | |||
| cfbef2585d | |||
| 95421271e3 | |||
| 752a28f24c | |||
| 05bda7ed0c | |||
| 1f6a94424d | |||
| 8626fa8bbe | |||
| 9f8daf9076 | |||
| 884bf15536 | |||
| 999da739a6 | |||
| d567d9c345 | |||
| cb51b82b8e | |||
| b0b522b4f8 | |||
| 3cf47abe19 | |||
| b7488acf89 | |||
| c9d723e0fb | |||
| c5fe2527b3 | |||
| 9c40c07941 | |||
| 9c7641f63f | |||
| 96a5cf5ba1 | |||
| 4b2146b12d | |||
| 6650957f79 | |||
| ec798a7c44 | |||
| b764fb1ded | |||
| 0a9fc171b0 | |||
| 9c9a972a0c | |||
| b474630c7d | |||
| f147050513 | |||
| daa8f3dd05 | |||
| be0ba75925 | |||
| 9ff3daf785 | |||
| 8babe05953 | |||
| 396795c983 | |||
| 9e907e6785 | |||
| 6d2239d8ac | |||
| 59e80c5103 | |||
| 69b1bdd32e | |||
| c05fad8b18 | |||
| 203df9d5b7 | |||
| 0d396ccd30 | |||
| ae8c1d229f | |||
| e7f51c2248 | |||
| 3238645f26 | |||
| f84f76f006 | |||
| 96c1300f15 | |||
| b6ba2dcdb0 | |||
| 5b0efc7e91 | |||
| d4070c3667 | |||
| a655aedb5c | |||
| 928ba56b95 | |||
| 8a6796e5c0 | |||
| 95b23095c0 | |||
| ffc9fb5007 | |||
| 6a7a8c803d | |||
| f990cf0064 | |||
| 1124908d2a | |||
| e1cba432a6 | |||
| 5c921cafeb | |||
| 697bbe59ca | |||
| 2a4819d75c | |||
| 5cfe6a0be1 | |||
| df421f1e4b | |||
| 61dd721ca2 | |||
| 3a9440f954 | |||
| e4c874d1af | |||
| 5594416121 | |||
| c4b3c901a5 | |||
| 617dd57162 | |||
| d044a8c99d | |||
| bf993528d1 | |||
| b8a9f7264d | |||
| b3b5b35889 | |||
| 80b5b24cce | |||
| 4a2afa9d02 | |||
| 4415f3151d | |||
| bf8cc50798 | |||
| ac208b1bcd | |||
| 8dfeabdd16 | |||
| 6d7696ef66 | |||
| 8e6e9fbc60 | |||
| 1429587dd4 | |||
| 0dbeeb4503 | |||
| 3ee0682247 | |||
| 249f559104 | |||
| ea6b0be910 | |||
| e779ecf12d | |||
| 01a22e61df | |||
| c8af90f697 | |||
| b5f03c95ab | |||
| d595ebc0e2 | |||
| d47e4dd1f7 | |||
| 83bcbee222 | |||
| 30034c75e7 | |||
| d3bd1a523d | |||
| 9665379cdc | |||
| a7000f8129 | |||
| a5d0491d57 | |||
| 12e3d210e8 | |||
| 632fadcea3 | |||
| 7c220b9af5 | |||
| f30b3ea329 | |||
| 9fbf7aa753 | |||
| 3e1840a75d | |||
| 4282668d47 | |||
| 4aab967796 | |||
| b87e601063 | |||
| a0e2e200d9 | |||
| 3133af2a21 | |||
| 5fe7ef63d8 | |||
| 62ed555b2c | |||
| 8aa2a34a8d | |||
| aaa590d846 | |||
| 1eb2b04aa1 | |||
| 2e75199664 | |||
| 0670f656fb | |||
| 4d17e3c209 | |||
| 58fb258016 | |||
| ed3ae9af59 | |||
| 64d5481c00 | |||
| cee52ea68f | |||
| d33b362d97 | |||
| c5acea75aa | |||
| 4f01f650dc | |||
| 3c869866c8 | |||
| ca1486b960 | |||
| dfca676d63 | |||
| 09d3c74c7f | |||
| c98147b31f | |||
| b41459055a | |||
| 84342b6215 | |||
| 24d63b6966 | |||
| 36f00db073 | |||
| 64f422b57f | |||
| dc9a2ed7eb | |||
| 980c38cebf | |||
| 31f6fd7131 | |||
| c9886fdc0f | |||
| 501a640330 | |||
| 477a26913a | |||
| 4ce474d3e4 | |||
| 3e6bf8b0f6 | |||
| e5aeca648a | |||
| 2b3fdf7ea8 | |||
| 0c7c37df6a | |||
| cde008b49f | |||
| 84d646bb5d | |||
| ccd803ff15 | |||
| 0528b91644 | |||
| 35b196d069 | |||
| 529e2b6796 | |||
| 3531e04117 | |||
| c3fde1c01c | |||
| 67b3f40eb2 | |||
| 19ce981296 | |||
| f4b8c5260c | |||
| cf343a1949 | |||
| 3ce105ae89 | |||
| ae44e071d8 | |||
| 2f14fa57e5 | |||
| f2b5519e76 | |||
| cc5de5df3c | |||
| d05245b5f5 | |||
| 084e8fe1b7 | |||
| 9f07a34955 | |||
| 6f22ec1e01 | |||
| c71ca34fd3 | |||
| 85b67d7f3d | |||
| 9e3f824bf5 | |||
| b94656a31f | |||
| 3cea85b2b0 | |||
| 4d0e3c98eb | |||
| cb22ee5681 | |||
| 9ab31e1fc6 | |||
| 1bd8d38c3c | |||
| 59607aa9ab | |||
| 48263be204 | |||
| 9f8110fe15 | |||
| 0edce6e1d1 | |||
| d95725d5fd | |||
| c98613b711 | |||
| 625b00e1da | |||
| b695aaec7c | |||
| f6dc5ad716 | |||
| 867322adf2 | |||
| 3288f525f3 | |||
| f2a2914d4f | |||
| e93d516672 | |||
| 2a47d66ee9 | |||
| 50c657f138 | |||
| 6070179009 | |||
| 4a4dcf1657 | |||
| 6efcaaf8d1 | |||
| f4af8e06b6 | |||
| 63c5eb7a82 | |||
| 248ad1acae | |||
| 905f99b18c | |||
| 11f85089cc | |||
| 5f1bf78555 | |||
| f374939cfd | |||
| eee2c4d4f2 | |||
| e11c1beb72 | |||
| 2bdfd2a31d | |||
| 271106d43f | |||
| 1fb7b51ee6 | |||
| c90cfe247c | |||
| 107aa4a66a | |||
| 9a1eda2caf | |||
| b5a5c76ceb | |||
| 4aad72d223 | |||
| 704961b378 | |||
| 054627adb0 | |||
| 058e7495f9 | |||
| 059532295d | |||
| 0c084399e2 | |||
| efb17b80a6 | |||
| 7890c7dc89 | |||
| b79e31e5a5 | |||
| 7172d167dd | |||
| 3cdf14d294 | |||
| a14c689fa6 | |||
| e143849f8a | |||
| 2e7a3bd702 |
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"globals": {
|
||||
"self": false
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"modules": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"spread": true,
|
||||
"templateStrings": true
|
||||
},
|
||||
"rules": {
|
||||
// Possible Errors //
|
||||
//-----------------//
|
||||
|
||||
"comma-dangle": [2, "never"],
|
||||
"no-cond-assign": [2, "except-parens"],
|
||||
|
||||
// Allow for debugging
|
||||
"no-console": 1,
|
||||
|
||||
"no-constant-condition": 2,
|
||||
"no-control-regex": 2,
|
||||
|
||||
// Allow for debugging
|
||||
"no-debugger": 1,
|
||||
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty": 2,
|
||||
"no-empty-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
|
||||
// Stylistic... might consider disallowing in the future
|
||||
"no-inner-declarations": 0,
|
||||
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-reserved-keys": 2, // Important for IE
|
||||
"no-sparse-arrays": 0,
|
||||
|
||||
// Optimizer and coverage will handle/highlight this and can be useful for debugging
|
||||
"no-unreachable": 1,
|
||||
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 0,
|
||||
"valid-typeof": 2,
|
||||
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
"block-scoped-var": 0,
|
||||
"complexity": 0,
|
||||
"consistent-return": 0,
|
||||
"curly": 2,
|
||||
"default-case": 1,
|
||||
"dot-notation": [2, {"allowKeywords": false}],
|
||||
"eqeqeq": 0,
|
||||
"guard-for-in": 1,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-div-regex": 1,
|
||||
"no-else-return": 0,
|
||||
"no-empty-label": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 1,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-param-reassign": 0,
|
||||
"no-process-env": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-void": 0,
|
||||
"no-warning-comments": 1,
|
||||
"no-with": 2,
|
||||
"radix": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": 2,
|
||||
"yoda": 0,
|
||||
|
||||
|
||||
// Strict //
|
||||
//--------//
|
||||
"strict": 0,
|
||||
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
"no-catch-shadow": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 0,
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
|
||||
"no-use-before-define": [2, "nofunc"],
|
||||
|
||||
|
||||
// Node.js //
|
||||
//---------//
|
||||
// Others left to environment defaults
|
||||
"no-mixed-requires": 0,
|
||||
|
||||
|
||||
// Stylistic //
|
||||
//-----------//
|
||||
"indent": 0,
|
||||
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
|
||||
"camelcase": 2,
|
||||
"comma-spacing": [2, {"before": false, "after": true}],
|
||||
"comma-style": [2, "last"],
|
||||
"consistent-this": [1, "self"],
|
||||
"eol-last": 2,
|
||||
"func-names": 0,
|
||||
"func-style": [2, "declaration"],
|
||||
"key-spacing": [2, {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}],
|
||||
"max-nested-callbacks": 0,
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-continue": 0,
|
||||
"no-inline-comments": 0,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multiple-empty-lines": 0,
|
||||
"no-nested-ternary": 1,
|
||||
"no-new-object": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-wrap-func": 2,
|
||||
"one-var": 0,
|
||||
"operator-assignment": 0,
|
||||
"padded-blocks": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"semi": 2,
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"sort-vars": 0,
|
||||
"space-after-keywords": [2, "always"],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
|
||||
"space-in-brackets": 0,
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-line-comment": 2,
|
||||
"wrap-regex": 1,
|
||||
|
||||
"no-var": 1
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ vendor
|
||||
lib/handlebars/compiler/parser.js
|
||||
/dist/
|
||||
/tmp/
|
||||
/coverage/
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = git://github.com/mustache/spec.git
|
||||
@@ -0,0 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**']
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
"equals",
|
||||
"test",
|
||||
"testBoth",
|
||||
"raises",
|
||||
"deepEqual",
|
||||
"start",
|
||||
"stop",
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module",
|
||||
"define",
|
||||
|
||||
"describe",
|
||||
"it",
|
||||
"afterEach",
|
||||
"beforeEach"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
"browser" : true,
|
||||
"esnext": true,
|
||||
|
||||
"boss" : true,
|
||||
"curly": false,
|
||||
"debug": false,
|
||||
"devel": false,
|
||||
"eqeqeq": false,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false
|
||||
}
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
.jshintrc
|
||||
.eslintrc
|
||||
.travis.yml
|
||||
.rspec
|
||||
Gemfile
|
||||
@@ -10,9 +10,11 @@ Rakefile
|
||||
Gruntfile.js
|
||||
*.gemspec
|
||||
*.nuspec
|
||||
*.log
|
||||
bench/*
|
||||
configurations/*
|
||||
components/*
|
||||
coverage/*
|
||||
dist/cdnjs/*
|
||||
dist/components/*
|
||||
spec/*
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# How to Contribute
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
|
||||
Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Generally we like to see pull requests that
|
||||
- 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)
|
||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
- Have tests
|
||||
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
|
||||
|
||||
## Building
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* [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`.
|
||||
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
|
||||
## 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.
|
||||
|
||||
```sh
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm test
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
yo release
|
||||
npm publish
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
gem push handlebars-source-*.gem
|
||||
```
|
||||
|
||||
After this point 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.
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://jsfiddle.net/9D88g/26/
|
||||
@@ -0,0 +1,60 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
1. How can I file a bug report:
|
||||
|
||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why isn't my Mustache template working?
|
||||
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
|
||||
1. Why is it slower when compiling?
|
||||
|
||||
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
|
||||
|
||||
1. Why doesn't this work with Content Security Policy restrictions?
|
||||
|
||||
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
|
||||
|
||||
1. How can I include script tags in my template?
|
||||
|
||||
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
|
||||
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
|
||||
1. Why are my precompiled scripts throwing exceptions?
|
||||
|
||||
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
|
||||
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
If using the integrated precompiler and
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
On the client side.
|
||||
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why doesn't IE like the `default` name in the AMD module?
|
||||
|
||||
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
|
||||
|
||||
1. How do I load the runtime library when using AMD?
|
||||
|
||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||
|
||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||
+73
-39
@@ -1,16 +1,16 @@
|
||||
var childProcess = require('child_process');
|
||||
|
||||
/*eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
jshint: {
|
||||
eslint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
files: [
|
||||
'dist/cjs/**/!(*.min|parser).js'
|
||||
'*.js',
|
||||
'lib/**/!(*.min|parser).js',
|
||||
'spec/**/!(*.amd|json2|require).js'
|
||||
]
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = function(grunt) {
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content, path) {
|
||||
processContent: function(content) {
|
||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
||||
+ content;
|
||||
}
|
||||
@@ -41,21 +41,14 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
|
||||
packager: {
|
||||
global: {
|
||||
type: 'global',
|
||||
export: 'Handlebars',
|
||||
files: [{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js'],
|
||||
dest: 'dist/'
|
||||
}]
|
||||
babel: {
|
||||
options: {
|
||||
loose: ['es6.modules']
|
||||
},
|
||||
|
||||
amd: {
|
||||
type: 'amd',
|
||||
anonymous: true,
|
||||
options: {
|
||||
modules: 'amd'
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
@@ -65,30 +58,61 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
cjs: {
|
||||
type: 'cjs',
|
||||
options: {
|
||||
modules: 'common'
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: '**/!(index).js',
|
||||
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' }
|
||||
]
|
||||
},
|
||||
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/"
|
||||
optimize: 'none',
|
||||
baseUrl: 'dist/amd/'
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
name: "handlebars",
|
||||
out: "dist/handlebars.amd.js"
|
||||
name: 'handlebars',
|
||||
out: 'dist/handlebars.amd.js'
|
||||
}
|
||||
},
|
||||
runtime: {
|
||||
options: {
|
||||
name: "handlebars.runtime",
|
||||
out: "dist/handlebars.runtime.amd.js"
|
||||
name: 'handlebars.runtime',
|
||||
out: 'dist/handlebars.runtime.amd.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -138,13 +162,22 @@ module.exports = function(grunt) {
|
||||
browsers: [
|
||||
{browserName: 'chrome'},
|
||||
{browserName: 'firefox'},
|
||||
{browserName: 'firefox', version: '3.6'},
|
||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
||||
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
|
||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
|
||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
|
||||
{browserName: 'internet explorer', version: 6, platform: 'XP'}
|
||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
|
||||
]
|
||||
}
|
||||
},
|
||||
sanity: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [
|
||||
{browserName: 'chrome'}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -163,18 +196,18 @@ module.exports = function(grunt) {
|
||||
});
|
||||
|
||||
// Build a new version of the library
|
||||
this.registerTask('build', "Builds a distributable version of the current project", [
|
||||
this.registerTask('build', 'Builds a distributable version of the current project', [
|
||||
'eslint',
|
||||
'parser',
|
||||
'node',
|
||||
'globals',
|
||||
'jshint']);
|
||||
'globals']);
|
||||
|
||||
this.registerTask('amd', ['packager:amd', 'requirejs']);
|
||||
this.registerTask('node', ['packager:cjs']);
|
||||
this.registerTask('globals', ['packager:global']);
|
||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||
this.registerTask('node', ['babel:cjs']);
|
||||
this.registerTask('globals', ['webpack']);
|
||||
this.registerTask('tests', ['concat:tests']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', ['amd', 'jshint', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
@@ -182,11 +215,12 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('es6-module-packager');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
|
||||
+49
-80
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Handlebars.js
|
||||
@@ -16,6 +16,8 @@ Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
|
||||
|
||||
For web browsers, a free CDN is available at [jsDelivr](http://www.jsdelivr.com/#!handlebarsjs). Advanced usage, such as [version aliasing & concocting](https://github.com/jsdelivr/jsdelivr#usage), is available.
|
||||
|
||||
Alternatively, if you prefer having the latest version of handlebars from
|
||||
the 'master' branch, passing builds of the 'master' branch are automatically
|
||||
published to S3. You may download the latest passing master build by grabbing
|
||||
@@ -108,7 +110,7 @@ templates easier and also changes a tiny detail of how partials work.
|
||||
### Paths
|
||||
|
||||
Handlebars.js supports an extended expression syntax that we call paths.
|
||||
Paths are made up of typical expressions and . characters. Expressions
|
||||
Paths are made up of typical expressions and `.` characters. Expressions
|
||||
allow you to not only display data from the current context, but to
|
||||
display data from contexts that are descendants and ancestors of the
|
||||
current context.
|
||||
@@ -132,7 +134,7 @@ into the person object you could still display the company's name with
|
||||
an expression like `{{../company.name}}`, so:
|
||||
|
||||
```
|
||||
{{#with person}}{{name}} - {{../company.name}}{{/person}}
|
||||
{{#with person}}{{name}} - {{../company.name}}{{/with}}
|
||||
```
|
||||
|
||||
would render:
|
||||
@@ -195,7 +197,7 @@ template(data);
|
||||
```
|
||||
|
||||
Whenever the block helper is called it is given one or more parameters,
|
||||
any arguments that are passed in the helper in the call and an `options`
|
||||
any arguments that are passed into the helper in the call, and an `options`
|
||||
object containing the `fn` function which executes the block's child.
|
||||
The block's current context may be accessed through `this`.
|
||||
|
||||
@@ -234,6 +236,35 @@ template(data);
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Partials can also accept parameters
|
||||
|
||||
```js
|
||||
var source = "<div>{{> roster rosterProperties people=listOfPeople}}</div>";
|
||||
|
||||
Handlebars.registerPartial('roster', '<h2>{{rosterName}}</h2>{{#people}}<span>{{id}}: {{name}}</span>{{/people}}')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = {
|
||||
"listOfPeople": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
],
|
||||
"rosterProperties": {
|
||||
"rosterName": "Cool People"
|
||||
}
|
||||
};
|
||||
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <div>
|
||||
// <h2>Cool People</h2>
|
||||
// <span>1: Alan</span>
|
||||
// <span>2: Yehuda</span>
|
||||
// </div>
|
||||
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
You can add comments to your templates with the following syntax:
|
||||
@@ -252,6 +283,14 @@ You can also use real html comments if you want them to end up in the output.
|
||||
```
|
||||
|
||||
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimeters are not supported.
|
||||
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
@@ -291,9 +330,8 @@ name sans the extension. These templates may be executed in the same
|
||||
manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single
|
||||
javascript method. To execute this method it must be passed to the using
|
||||
the `Handlebars.template` method and the resulting object may be as
|
||||
normal.
|
||||
javascript method. To execute this method it must be passed to
|
||||
the `Handlebars.template` method and the resulting object may be used as normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
@@ -336,13 +374,7 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
and we will have some benchmarks in the near future.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
|
||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
|
||||
Upgrading
|
||||
@@ -352,16 +384,9 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another.
|
||||
* Handlebars.js can be cryptic when there's an error while rendering.
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
Reporting Issues
|
||||
----------------
|
||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
|
||||
Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version.
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Handlebars in the Wild
|
||||
----------------------
|
||||
@@ -370,6 +395,7 @@ Handlebars in the Wild
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
structure your views, also with automatic data binding support.
|
||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
@@ -377,6 +403,7 @@ Handlebars in the Wild
|
||||
from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
||||
with 100+ handlebars helpers.
|
||||
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extendible and embeddable layout blocks as seen in other popular templating languages.
|
||||
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
||||
from Don Park.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
||||
@@ -402,68 +429,10 @@ External Resources
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request][pull-request]!
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
Feel free to contact commondream or wycats through GitHub with any other
|
||||
questions or feature requests. To submit changes fork the project and
|
||||
send a pull request.
|
||||
|
||||
### 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.
|
||||
|
||||
```sh
|
||||
grunt build release
|
||||
export HANDLEBARS_PATH=`pwd`
|
||||
|
||||
cd $emberRepoDir
|
||||
bundle exec rake clean
|
||||
bundle exec rake test
|
||||
```
|
||||
|
||||
### Releasing
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
yo release
|
||||
npm publish
|
||||
yo release:publish cdnjs handlebars.js dist/cdnjs/
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
gem push handlebars-source-*.gem
|
||||
```
|
||||
|
||||
After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution.
|
||||
|
||||
License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://l.kde.cc/hbs-bug
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
|
||||
dust: "{person.name}{person.age}{person.foo}{animal.age}",
|
||||
eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
||||
mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
|
||||
context: { person: { name: {bar: {baz: "Larry"}}, age: 45 } },
|
||||
handlebars: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}",
|
||||
dust: "{person.name.bar.baz}{person.age}{person.foo}{animal.age}",
|
||||
eco: "<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
||||
mustache: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}"
|
||||
};
|
||||
|
||||
@@ -28,11 +28,13 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
partials = template.partials,
|
||||
|
||||
handlebarsOut,
|
||||
compatOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, name) {
|
||||
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
|
||||
@@ -43,6 +45,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
compatOut = compat(context, options);
|
||||
bench("compat", function() {
|
||||
compat(context, options);
|
||||
});
|
||||
|
||||
if (handlebarsOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -107,6 +114,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
}
|
||||
}
|
||||
|
||||
compare(compatOut, 'compat');
|
||||
compare(dustOut, 'dust');
|
||||
compare(ecoOut, 'eco');
|
||||
compare(mustacheOut, 'mustache');
|
||||
|
||||
+18
-142
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'map': {
|
||||
'type': 'string',
|
||||
'description': 'Source Map File'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
@@ -22,7 +26,7 @@ var optimist = require('optimist')
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
@@ -80,6 +84,11 @@ var optimist = require('optimist')
|
||||
'type': 'boolean',
|
||||
'description': 'Prints the current compiler version',
|
||||
'alias': 'version'
|
||||
},
|
||||
|
||||
'help': {
|
||||
'type': 'boolean',
|
||||
'description': 'Outputs this message'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -87,149 +96,16 @@ var optimist = require('optimist')
|
||||
if (argv.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
var template = [0];
|
||||
if (!argv._.length) {
|
||||
throw 'Must define at least one template or directory.';
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw 'Unable to open template file "' + template + '"';
|
||||
}
|
||||
});
|
||||
})
|
||||
.check(function(argv) {
|
||||
if (argv.simple && argv.min) {
|
||||
throw 'Unable to minimze simple output';
|
||||
}
|
||||
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
|
||||
throw 'Unable to output multiple templates in simple mode';
|
||||
}
|
||||
});
|
||||
|
||||
var fs = require('fs'),
|
||||
handlebars = require('../lib'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var argv = optimist.argv,
|
||||
template = argv._[0];
|
||||
var argv = optimist.argv;
|
||||
argv.templates = argv._;
|
||||
delete argv._;
|
||||
|
||||
if (argv.version) {
|
||||
return console.log(handlebars.VERSION);
|
||||
if (argv.help || (!argv.templates.length && !argv.version)) {
|
||||
optimist.showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the known list into a hash
|
||||
var known = {};
|
||||
if (argv.known && !Array.isArray(argv.known)) {
|
||||
argv.known = [argv.known];
|
||||
}
|
||||
if (argv.known) {
|
||||
for (var i = 0, len = argv.known.length; i < len; i++) {
|
||||
known[argv.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (argv.commonjs) {
|
||||
output.push('var Handlebars = require("' + argv.commonjs + '");');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' || {};\n');
|
||||
}
|
||||
function processTemplate(template, root, explicit) {
|
||||
var path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
var path = template + '/' + file;
|
||||
|
||||
if (extension.test(path) || fs.statSync(path).isDirectory()) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
} else if (explicit || extension.test(path)) {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (argv.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: argv.o
|
||||
};
|
||||
|
||||
if (argv.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length+1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
if (argv.simple) {
|
||||
output.push(handlebars.precompile(data, options) + '\n');
|
||||
} else if (argv.partial) {
|
||||
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
} else {
|
||||
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
processTemplate(template, argv.root, true);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
|
||||
if(argv.partial){
|
||||
output.push('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.push('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.push('});');
|
||||
} else if (!argv.commonjs) {
|
||||
output.push('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
if (argv.min) {
|
||||
output = uglify.minify(output, {fromString: true}).code;
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
fs.writeFileSync(argv.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
return require('../dist/cjs/precompiler').cli(argv);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "3.0.3",
|
||||
"main": "handlebars.js",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.version = package["version"]
|
||||
gem.version = package["version"].sub "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
gem.files = [
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>2.0.0-alpha.4</version>
|
||||
<version>3.0.3</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
# Handlebars Compiler APIs
|
||||
|
||||
There are a number of formal APIs that tool implementors may interact with.
|
||||
|
||||
## AST
|
||||
|
||||
Other tools may interact with the formal AST as defined below. Any JSON structure matching this pattern may be used and passed into the `compile` and `precompile` methods in the same way as the text for a template.
|
||||
|
||||
AST structures may be generated either with the `Handlebars.parse` method and then manipulated, via the `Handlebars.AST` objects of the same name, or constructed manually as a generic JavaScript object matching the structure defined below.
|
||||
|
||||
```javascript
|
||||
var ast = Handlebars.parse(myTemplate);
|
||||
|
||||
// Modify ast
|
||||
|
||||
Handlebars.precompile(ast);
|
||||
```
|
||||
|
||||
|
||||
### Basic
|
||||
|
||||
```java
|
||||
interface Node {
|
||||
type: string;
|
||||
loc: SourceLocation | null;
|
||||
}
|
||||
|
||||
interface SourceLocation {
|
||||
source: string | null;
|
||||
start: Position;
|
||||
end: Position;
|
||||
}
|
||||
|
||||
interface Position {
|
||||
line: uint >= 1;
|
||||
column: uint >= 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Programs
|
||||
|
||||
```java
|
||||
interface Program <: Node {
|
||||
type: "Program";
|
||||
body: [ Statement ];
|
||||
|
||||
blockParams: [ string ];
|
||||
}
|
||||
```
|
||||
|
||||
### Statements
|
||||
|
||||
```java
|
||||
interface Statement <: Node { }
|
||||
|
||||
interface MustacheStatement <: Statement {
|
||||
type: "MustacheStatement";
|
||||
|
||||
path: PathExpression | Literal;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
escaped: boolean;
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface BlockStatement <: Statement {
|
||||
type: "BlockStatement";
|
||||
path: PathExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
program: Program | null;
|
||||
inverse: Program | null;
|
||||
|
||||
openStrip: StripFlags | null;
|
||||
inverseStrip: StripFlags | null;
|
||||
closeStrip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface PartialStatement <: Statement {
|
||||
type: "PartialStatement";
|
||||
name: PathExpression | SubExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
indent: string;
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
|
||||
|
||||
|
||||
```java
|
||||
interface ContentStatement <: Statement {
|
||||
type: "ContentStatement";
|
||||
value: string;
|
||||
original: string;
|
||||
}
|
||||
|
||||
interface CommentStatement <: Statement {
|
||||
type: "CommentStatement";
|
||||
value: string;
|
||||
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
### Expressions
|
||||
|
||||
```java
|
||||
interface Expression <: Node { }
|
||||
```
|
||||
|
||||
##### SubExpressions
|
||||
|
||||
```java
|
||||
interface SubExpression <: Expression {
|
||||
type: "SubExpression";
|
||||
path: PathExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
}
|
||||
```
|
||||
|
||||
##### Paths
|
||||
|
||||
```java
|
||||
interface PathExpression <: Expression {
|
||||
type: "PathExpression";
|
||||
data: boolean;
|
||||
depth: uint >= 0;
|
||||
parts: [ string ];
|
||||
original: string;
|
||||
}
|
||||
```
|
||||
|
||||
- `data` is true when the given expression is a `@data` reference.
|
||||
- `depth` is an integer representation of which context the expression references. `0` represents the current context, `1` would be `../`, etc.
|
||||
- `parts` is an array of the names in the path. `foo.bar` would be `['foo', 'bar']`. Scope references, `.`, `..`, and `this` should be omitted from this array.
|
||||
- `original` is the path as entered by the user. Separator and scope references are left untouched.
|
||||
|
||||
|
||||
##### Literals
|
||||
|
||||
```java
|
||||
interface Literal <: Expression { }
|
||||
|
||||
interface StringLiteral <: Literal {
|
||||
type: "StringLiteral";
|
||||
value: string;
|
||||
original: string;
|
||||
}
|
||||
|
||||
interface BooleanLiteral <: Literal {
|
||||
type: "BooleanLiteral";
|
||||
value: boolean;
|
||||
original: boolean;
|
||||
}
|
||||
|
||||
interface NumberLiteral <: Literal {
|
||||
type: "NumberLiteral";
|
||||
value: number;
|
||||
original: number;
|
||||
}
|
||||
|
||||
interface UndefinedLiteral <: Literal {
|
||||
type: "UndefinedLiteral";
|
||||
}
|
||||
|
||||
interface NullLiteral <: Literal {
|
||||
type: "NullLiteral";
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
```java
|
||||
interface Hash <: Node {
|
||||
type: "Hash";
|
||||
pairs: [ HashPair ];
|
||||
}
|
||||
|
||||
interface HashPair <: Node {
|
||||
type: "HashPair";
|
||||
key: string;
|
||||
value: Expression;
|
||||
}
|
||||
|
||||
interface StripFlags {
|
||||
open: boolean;
|
||||
close: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
`StripFlags` are used to signify whitespace control character that may have been entered on a given statement.
|
||||
|
||||
## AST Visitor
|
||||
|
||||
`Handlebars.Visitor` is available as a base class for general interaction with AST structures. This will by default traverse the entire tree and individual methods may be overridden to provide specific responses to particular nodes.
|
||||
|
||||
Recording all referenced partial names:
|
||||
|
||||
```javascript
|
||||
var Visitor = Handlebars.Visitor;
|
||||
|
||||
function ImportScanner() {
|
||||
this.partials = [];
|
||||
}
|
||||
ImportScanner.prototype = new Visitor();
|
||||
|
||||
ImportScanner.prototype.PartialStatement = function(partial) {
|
||||
this.partials.push({request: partial.name.original});
|
||||
|
||||
Visitor.prototype.PartialStatement.call(this, partial);
|
||||
};
|
||||
|
||||
var scanner = new ImportScanner();
|
||||
scanner.accept(ast);
|
||||
```
|
||||
|
||||
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
|
||||
|
||||
The visitor may also be configured to operate in mutation mode by setting the `mutation` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node in tact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
|
||||
|
||||
Implementors that may need to support mutation mode are encouraged to utilize the `acceptKey`, `acceptRequired` and `acceptArray` helpers which provide the conditional overwrite behavior as well as implement sanity checks where pertinent.
|
||||
|
||||
## JavaScript Compiler
|
||||
|
||||
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the compiler:
|
||||
|
||||
- `nameLookup(parent, name, type)`
|
||||
Used to generate the code to resolve a give path component.
|
||||
|
||||
- `parent` is the existing code in the path resolution
|
||||
- `name` is the current path component
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
|
||||
|
||||
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
|
||||
|
||||
- `depthedLookup(name)`
|
||||
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
|
||||
|
||||
- `compilerInfo()`
|
||||
Allows for custom compiler flags used in the runtime version checking logic.
|
||||
|
||||
- `appendToBuffer(source, location, explicit)`
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
|
||||
- `initializeBuffer()`
|
||||
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||
|
||||
```javascript
|
||||
function MyCompiler() {
|
||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||
}
|
||||
MyCompiler.prototype = Object.create(Handlebars.JavaScriptCompiler);
|
||||
|
||||
MyCompiler.nameLookup = function(parent, name, type) {
|
||||
if (type === 'partial') {
|
||||
return 'MyPartialList[' + JSON.stringify(name) ']';
|
||||
} else {
|
||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
||||
}
|
||||
};
|
||||
|
||||
var env = Handlebars.create();
|
||||
env.JavaScriptCompiler = MyCompiler;
|
||||
env.compile('my template');
|
||||
```
|
||||
+22
-14
@@ -1,20 +1,22 @@
|
||||
/*globals Handlebars: true */
|
||||
import Handlebars from "./handlebars.runtime";
|
||||
import runtime from './handlebars.runtime';
|
||||
|
||||
// Compiler imports
|
||||
import AST from "./handlebars/compiler/ast";
|
||||
import { parser as Parser, parse } from "./handlebars/compiler/base";
|
||||
import { Compiler, compile, precompile } from "./handlebars/compiler/compiler";
|
||||
import JavaScriptCompiler from "./handlebars/compiler/javascript-compiler";
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import Visitor from './handlebars/compiler/visitor';
|
||||
|
||||
var _create = Handlebars.create;
|
||||
var create = function() {
|
||||
var hb = _create();
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
|
||||
let _create = runtime.create;
|
||||
function create() {
|
||||
let hb = _create();
|
||||
|
||||
hb.compile = function(input, options) {
|
||||
return compile(input, options, hb);
|
||||
};
|
||||
hb.precompile = function (input, options) {
|
||||
hb.precompile = function(input, options) {
|
||||
return precompile(input, options, hb);
|
||||
};
|
||||
|
||||
@@ -25,9 +27,15 @@ var create = function() {
|
||||
hb.parse = parse;
|
||||
|
||||
return hb;
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
let inst = create();
|
||||
inst.create = create;
|
||||
|
||||
export default Handlebars;
|
||||
noConflict(inst);
|
||||
|
||||
inst.Visitor = Visitor;
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
export default inst;
|
||||
|
||||
+18
-12
@@ -1,21 +1,23 @@
|
||||
/*globals Handlebars: true */
|
||||
module base from "./handlebars/base";
|
||||
import * as base from './handlebars/base';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from "./handlebars/safe-string";
|
||||
import Exception from "./handlebars/exception";
|
||||
module Utils from "./handlebars/utils";
|
||||
module runtime from "./handlebars/runtime";
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import Exception from './handlebars/exception';
|
||||
import * as Utils from './handlebars/utils';
|
||||
import * as runtime from './handlebars/runtime';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
var create = function() {
|
||||
var hb = new base.HandlebarsEnvironment();
|
||||
function create() {
|
||||
let hb = new base.HandlebarsEnvironment();
|
||||
|
||||
Utils.extend(hb, base);
|
||||
hb.SafeString = SafeString;
|
||||
hb.Exception = Exception;
|
||||
hb.Utils = Utils;
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
@@ -23,9 +25,13 @@ var create = function() {
|
||||
};
|
||||
|
||||
return hb;
|
||||
};
|
||||
}
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
let inst = create();
|
||||
inst.create = create;
|
||||
|
||||
export default Handlebars;
|
||||
noConflict(inst);
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
export default inst;
|
||||
|
||||
+88
-74
@@ -1,21 +1,22 @@
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
|
||||
export var VERSION = "2.0.0-alpha.4";
|
||||
export var COMPILER_REVISION = 5;
|
||||
export const VERSION = '3.0.1';
|
||||
export const COMPILER_REVISION = 6;
|
||||
|
||||
export var REVISION_CHANGES = {
|
||||
export const REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '== 1.x.x',
|
||||
5: '>= 2.0.0'
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1'
|
||||
};
|
||||
|
||||
var isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
const isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
export function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
@@ -30,12 +31,11 @@ HandlebarsEnvironment.prototype = {
|
||||
logger: logger,
|
||||
log: log,
|
||||
|
||||
registerHelper: function(name, fn, inverse) {
|
||||
registerHelper: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
@@ -43,11 +43,14 @@ HandlebarsEnvironment.prototype = {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, str) {
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
if (typeof partial === 'undefined') {
|
||||
throw new Exception('Attempting to register a partial as undefined');
|
||||
}
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
@@ -57,26 +60,25 @@ HandlebarsEnvironment.prototype = {
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if(arguments.length === 1) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
|
||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
} else if (context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if(context.length > 0) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
@@ -87,7 +89,7 @@ function registerDefaultHelpers(instance) {
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
@@ -97,16 +99,17 @@ function registerDefaultHelpers(instance) {
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
// Allow for {{#each}}
|
||||
if (!options) {
|
||||
options = context;
|
||||
context = this;
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
var contextPath;
|
||||
if (options.data && options.ids) {
|
||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
@@ -117,40 +120,51 @@ function registerDefaultHelpers(instance) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
data.last = (i === (context.length-1));
|
||||
function execIteration(field, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + i;
|
||||
}
|
||||
}
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ret + fn(context[field], {
|
||||
data: data,
|
||||
blockParams: Utils.blockParams([context[field], field], [contextPath + field, null])
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) {
|
||||
data.key = key;
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
let priorKey;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + key;
|
||||
}
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(key)) {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
priorKey = key;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
if (i === 0) {
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
@@ -177,30 +191,32 @@ function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
var fn = options.fn;
|
||||
let fn = options.fn;
|
||||
|
||||
if (!Utils.isEmpty(context)) {
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
options = {data:data};
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, context);
|
||||
instance.registerHelper('log', function(message, options) {
|
||||
let level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, message);
|
||||
});
|
||||
|
||||
instance.registerHelper('lookup', function(obj, field, options) {
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
|
||||
export var logger = {
|
||||
export let logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
@@ -208,23 +224,21 @@ export var logger = {
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 3,
|
||||
level: 1,
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (logger.level <= level) {
|
||||
var method = logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, message) {
|
||||
if (typeof console !== 'undefined' && logger.level <= level) {
|
||||
let method = logger.methodMap[level];
|
||||
(console[method] || console.log).call(console, message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function log(level, obj) { logger.log(level, obj); }
|
||||
export let log = logger.log;
|
||||
|
||||
export var createFrame = function(object) {
|
||||
var frame = Utils.extend({}, object);
|
||||
export function createFrame(object) {
|
||||
let frame = Utils.extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
};
|
||||
}
|
||||
|
||||
+132
-226
@@ -1,246 +1,152 @@
|
||||
import Exception from "../exception";
|
||||
let AST = {
|
||||
Program: function(statements, blockParams, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Program';
|
||||
this.body = statements;
|
||||
|
||||
function LocationInfo(locInfo){
|
||||
locInfo = locInfo || {};
|
||||
this.firstLine = locInfo.first_line;
|
||||
this.firstColumn = locInfo.first_column;
|
||||
this.lastColumn = locInfo.last_column;
|
||||
this.lastLine = locInfo.last_line;
|
||||
}
|
||||
|
||||
var AST = {
|
||||
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
|
||||
var inverseLocationInfo, firstInverseNode;
|
||||
if (arguments.length === 3) {
|
||||
locInfo = inverse;
|
||||
inverse = null;
|
||||
} else if (arguments.length === 2) {
|
||||
locInfo = inverseStrip;
|
||||
inverseStrip = null;
|
||||
}
|
||||
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
this.strip = {};
|
||||
|
||||
if(inverse) {
|
||||
firstInverseNode = inverse[0];
|
||||
if (firstInverseNode) {
|
||||
inverseLocationInfo = {
|
||||
first_line: firstInverseNode.firstLine,
|
||||
last_line: firstInverseNode.lastLine,
|
||||
last_column: firstInverseNode.lastColumn,
|
||||
first_column: firstInverseNode.firstColumn
|
||||
};
|
||||
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
|
||||
} else {
|
||||
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
|
||||
}
|
||||
this.strip.right = inverseStrip.left;
|
||||
} else if (inverseStrip) {
|
||||
this.strip.left = inverseStrip.right;
|
||||
}
|
||||
},
|
||||
|
||||
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "mustache";
|
||||
this.blockParams = blockParams;
|
||||
this.strip = strip;
|
||||
|
||||
// Open may be a string parsed from the parser or a passed boolean flag
|
||||
if (open != null && open.charAt) {
|
||||
// Must use charAt to support IE pre-10
|
||||
var escapeFlag = open.charAt(3) || open.charAt(2);
|
||||
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
} else {
|
||||
this.escaped = !!open;
|
||||
}
|
||||
|
||||
if (rawParams instanceof AST.SexprNode) {
|
||||
this.sexpr = rawParams;
|
||||
} else {
|
||||
// Support old AST API
|
||||
this.sexpr = new AST.SexprNode(rawParams, hash);
|
||||
}
|
||||
|
||||
this.sexpr.isRoot = true;
|
||||
|
||||
// Support old AST API that stored this info in MustacheNode
|
||||
this.id = this.sexpr.id;
|
||||
this.params = this.sexpr.params;
|
||||
this.hash = this.sexpr.hash;
|
||||
this.eligibleHelper = this.sexpr.eligibleHelper;
|
||||
this.isHelper = this.sexpr.isHelper;
|
||||
},
|
||||
|
||||
SexprNode: function(rawParams, hash, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
MustacheStatement: function(path, params, hash, escaped, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'MustacheStatement';
|
||||
|
||||
this.type = "sexpr";
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.escaped = escaped;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BlockStatement';
|
||||
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.openStrip = openStrip;
|
||||
this.inverseStrip = inverseStrip;
|
||||
this.closeStrip = closeStrip;
|
||||
},
|
||||
|
||||
PartialStatement: function(name, params, hash, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PartialStatement';
|
||||
|
||||
this.name = name;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
this.indent = '';
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
ContentStatement: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'ContentStatement';
|
||||
this.original = this.value = string;
|
||||
},
|
||||
|
||||
CommentStatement: function(comment, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'CommentStatement';
|
||||
this.value = comment;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
SubExpression: function(path, params, hash, locInfo) {
|
||||
this.loc = locInfo;
|
||||
|
||||
this.type = 'SubExpression';
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
},
|
||||
|
||||
PathExpression: function(data, depth, parts, original, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PathExpression';
|
||||
|
||||
this.data = data;
|
||||
this.original = original;
|
||||
this.parts = parts;
|
||||
this.depth = depth;
|
||||
},
|
||||
|
||||
StringLiteral: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'StringLiteral';
|
||||
this.original =
|
||||
this.value = string;
|
||||
},
|
||||
|
||||
NumberLiteral: function(number, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NumberLiteral';
|
||||
this.original =
|
||||
this.value = Number(number);
|
||||
},
|
||||
|
||||
BooleanLiteral: function(bool, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BooleanLiteral';
|
||||
this.original =
|
||||
this.value = bool === 'true';
|
||||
},
|
||||
|
||||
UndefinedLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'UndefinedLiteral';
|
||||
this.original = this.value = undefined;
|
||||
},
|
||||
|
||||
NullLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NullLiteral';
|
||||
this.original = this.value = null;
|
||||
},
|
||||
|
||||
Hash: function(pairs, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Hash';
|
||||
this.pairs = pairs;
|
||||
},
|
||||
HashPair: function(key, value, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'HashPair';
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
},
|
||||
|
||||
// Public API used to evaluate derived attributes regarding AST nodes
|
||||
helpers: {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = !!(params.length || hash);
|
||||
helperExpression: function(node) {
|
||||
return !!(node.type === 'SubExpression' || node.params.length || node.hash);
|
||||
},
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
this.eligibleHelper = this.isHelper || id.isSimple;
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
},
|
||||
|
||||
PartialNode: function(partialName, context, hash, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
BlockNode: function(mustache, program, inverse, close, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
if(mustache.sexpr.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
|
||||
}
|
||||
|
||||
this.type = 'block';
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.strip = {
|
||||
left: mustache.strip.left,
|
||||
right: close.strip.right
|
||||
};
|
||||
|
||||
(program || inverse).strip.left = mustache.strip.right;
|
||||
(inverse || program).strip.right = close.strip.left;
|
||||
|
||||
if (inverse && !program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
},
|
||||
|
||||
RawBlockNode: function(mustache, content, close, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
if (mustache.sexpr.id.original !== close) {
|
||||
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
|
||||
}
|
||||
|
||||
content = new AST.ContentNode(content, locInfo);
|
||||
|
||||
this.type = 'block';
|
||||
this.mustache = mustache;
|
||||
this.program = new AST.ProgramNode([content], locInfo);
|
||||
},
|
||||
|
||||
ContentNode: function(string, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "content";
|
||||
this.string = string;
|
||||
},
|
||||
|
||||
HashNode: function(pairs, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
},
|
||||
|
||||
IdNode: function(parts, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "ID";
|
||||
|
||||
var original = "",
|
||||
dig = [],
|
||||
depth = 0,
|
||||
depthString = '';
|
||||
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i].part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (part === ".." || part === "." || part === "this") {
|
||||
if (dig.length > 0) {
|
||||
throw new Exception("Invalid path: " + original, this);
|
||||
} else if (part === "..") {
|
||||
depth++;
|
||||
depthString += '../';
|
||||
} else {
|
||||
this.isScoped = true;
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
this.original = original;
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
this.idName = depthString + this.string;
|
||||
scopedId: function(path) {
|
||||
return (/^\.|this\b/).test(path.original);
|
||||
},
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
|
||||
this.stringModeValue = this.string;
|
||||
},
|
||||
|
||||
PartialNameNode: function(name, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name.original;
|
||||
},
|
||||
|
||||
DataNode: function(id, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
this.stringModeValue = id.stringModeValue;
|
||||
this.idName = '@' + id.stringModeValue;
|
||||
},
|
||||
|
||||
StringNode: function(string, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "STRING";
|
||||
this.original =
|
||||
this.string =
|
||||
this.stringModeValue = string;
|
||||
},
|
||||
|
||||
NumberNode: function(number, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "NUMBER";
|
||||
this.original =
|
||||
this.number = number;
|
||||
this.stringModeValue = Number(number);
|
||||
},
|
||||
|
||||
BooleanNode: function(bool, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
},
|
||||
|
||||
CommentNode: function(comment, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
simpleId: function(path) {
|
||||
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||
// most modify the object to operate properly.
|
||||
// must modify the object to operate properly.
|
||||
export default AST;
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import parser from "./parser";
|
||||
import AST from "./ast";
|
||||
import parser from './parser';
|
||||
import AST from './ast';
|
||||
import WhitespaceControl from './whitespace-control';
|
||||
import * as Helpers from './helpers';
|
||||
import { extend } from '../utils';
|
||||
|
||||
export { parser };
|
||||
|
||||
export function parse(input) {
|
||||
// Just return if an already-compile AST was passed in.
|
||||
if(input.constructor === AST.ProgramNode) { return input; }
|
||||
let yy = {};
|
||||
extend(yy, Helpers, AST);
|
||||
|
||||
parser.yy = AST;
|
||||
return parser.parse(input);
|
||||
export function parse(input, options) {
|
||||
// Just return if an already-compiled AST was passed in.
|
||||
if (input.type === 'Program') { return input; }
|
||||
|
||||
parser.yy = yy;
|
||||
|
||||
// Altering the shared object here, but this is ok as parser is a sync operation
|
||||
yy.locInfo = function(locInfo) {
|
||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||
};
|
||||
|
||||
let strip = new WhitespaceControl();
|
||||
return strip.accept(parser.parse(input));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*global define */
|
||||
import {isArray} from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
|
||||
try {
|
||||
/* istanbul ignore next */
|
||||
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');
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
}
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
|
||||
if (!SourceNode) {
|
||||
SourceNode = function(line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function() {
|
||||
return {code: this.toString()};
|
||||
},
|
||||
toString: function() {
|
||||
return this.src;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
let ret = [];
|
||||
|
||||
for (let i = 0, len = chunk.length; i < len; i++) {
|
||||
ret.push(codeGen.wrap(chunk[i], loc));
|
||||
}
|
||||
return ret;
|
||||
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
|
||||
// Handle primitives that the SourceNode will throw up on
|
||||
return chunk + '';
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
||||
function CodeGen(srcFile) {
|
||||
this.srcFile = srcFile;
|
||||
this.source = [];
|
||||
}
|
||||
|
||||
CodeGen.prototype = {
|
||||
prepend: function(source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
push: function(source, loc) {
|
||||
this.source.push(this.wrap(source, loc));
|
||||
},
|
||||
|
||||
merge: function() {
|
||||
let source = this.empty();
|
||||
this.each(function(line) {
|
||||
source.add([' ', line, '\n']);
|
||||
});
|
||||
return source;
|
||||
},
|
||||
|
||||
each: function(iter) {
|
||||
for (let i = 0, len = this.source.length; i < len; i++) {
|
||||
iter(this.source[i]);
|
||||
}
|
||||
},
|
||||
|
||||
empty: function(loc = this.currentLocation || {start: {}}) {
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
|
||||
if (chunk instanceof SourceNode) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
chunk = castChunk(chunk, this, loc);
|
||||
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
|
||||
},
|
||||
|
||||
functionCall: function(fn, type, params) {
|
||||
params = this.generateList(params);
|
||||
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + (str + '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
let pairs = [];
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
let value = castChunk(obj[key], this);
|
||||
if (value !== 'undefined') {
|
||||
pairs.push([this.quotedString(key), ':', value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ret = this.generateList(pairs);
|
||||
ret.prepend('{');
|
||||
ret.add('}');
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
||||
generateList: function(entries, loc) {
|
||||
let ret = this.empty(loc);
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
if (i) {
|
||||
ret.add(',');
|
||||
}
|
||||
|
||||
ret.add(castChunk(entries[i], this, loc));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries, loc) {
|
||||
let ret = this.generateList(entries, loc);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
export default CodeGen;
|
||||
|
||||
+293
-253
@@ -1,4 +1,8 @@
|
||||
import Exception from "../exception";
|
||||
import Exception from '../exception';
|
||||
import {isArray, indexOf} from '../utils';
|
||||
import AST from './ast';
|
||||
|
||||
const slice = [].slice;
|
||||
|
||||
export function Compiler() {}
|
||||
|
||||
@@ -10,54 +14,24 @@ export function Compiler() {}
|
||||
Compiler.prototype = {
|
||||
compiler: Compiler,
|
||||
|
||||
disassemble: function() {
|
||||
var opcodes = this.opcodes, opcode, out = [], params, param;
|
||||
|
||||
for (var i=0, l=opcodes.length; i<l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
if (opcode.opcode === 'DECLARE') {
|
||||
out.push("DECLARE " + opcode.name + "=" + opcode.value);
|
||||
} else {
|
||||
params = [];
|
||||
for (var j=0; j<opcode.args.length; j++) {
|
||||
param = opcode.args[j];
|
||||
if (typeof param === "string") {
|
||||
param = "\"" + param.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
params.push(param);
|
||||
}
|
||||
out.push(opcode.opcode + " " + params.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
return out.join("\n");
|
||||
},
|
||||
|
||||
equals: function(other) {
|
||||
var len = this.opcodes.length;
|
||||
let len = this.opcodes.length;
|
||||
if (other.opcodes.length !== len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
var opcode = this.opcodes[i],
|
||||
for (let i = 0; i < len; i++) {
|
||||
let opcode = this.opcodes[i],
|
||||
otherOpcode = other.opcodes[i];
|
||||
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
|
||||
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
|
||||
return false;
|
||||
}
|
||||
for (var j = 0; j < opcode.args.length; j++) {
|
||||
if (opcode.args[j] !== otherOpcode.args[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We know that length is the same between the two arrays because they are directly tied
|
||||
// to the opcode behavior above.
|
||||
len = this.children.length;
|
||||
if (other.children.length !== len) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (!this.children[i].equals(other.children[i])) {
|
||||
return false;
|
||||
}
|
||||
@@ -69,16 +43,18 @@ Compiler.prototype = {
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
this.sourceNode = [];
|
||||
this.opcodes = [];
|
||||
this.children = [];
|
||||
this.depths = {list: []};
|
||||
this.options = options;
|
||||
this.stringParams = options.stringParams;
|
||||
this.trackIds = options.trackIds;
|
||||
|
||||
options.blockParams = options.blockParams || [];
|
||||
|
||||
// These changes will propagate to the other compiler components
|
||||
var knownHelpers = this.options.knownHelpers;
|
||||
this.options.knownHelpers = {
|
||||
let knownHelpers = options.knownHelpers;
|
||||
options.knownHelpers = {
|
||||
'helperMissing': true,
|
||||
'blockHelperMissing': true,
|
||||
'each': true,
|
||||
@@ -89,92 +65,77 @@ Compiler.prototype = {
|
||||
'lookup': true
|
||||
};
|
||||
if (knownHelpers) {
|
||||
for (var name in knownHelpers) {
|
||||
this.options.knownHelpers[name] = knownHelpers[name];
|
||||
for (let name in knownHelpers) {
|
||||
if (name in knownHelpers) {
|
||||
options.knownHelpers[name] = knownHelpers[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.accept(program);
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
var strip = node.strip || {},
|
||||
ret;
|
||||
if (strip.left) {
|
||||
this.opcode('strip');
|
||||
}
|
||||
|
||||
ret = this[node.type](node);
|
||||
|
||||
if (strip.right) {
|
||||
this.opcode('strip');
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
program: function(program) {
|
||||
var statements = program.statements;
|
||||
|
||||
for(var i=0, l=statements.length; i<l; i++) {
|
||||
this.accept(statements[i]);
|
||||
}
|
||||
this.isSimple = l === 1;
|
||||
|
||||
this.depths.list = this.depths.list.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
var result = new this.compiler().compile(program, this.options);
|
||||
var guid = this.guid++, depth;
|
||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
|
||||
this.usePartial = this.usePartial || result.usePartial;
|
||||
|
||||
this.children[guid] = result;
|
||||
|
||||
for(var i=0, l=result.depths.list.length; i<l; i++) {
|
||||
depth = result.depths.list[i];
|
||||
|
||||
if(depth < 2) { continue; }
|
||||
else { this.addDepth(depth - 1); }
|
||||
}
|
||||
this.useDepths = this.useDepths || result.useDepths;
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
block: function(block) {
|
||||
var mustache = block.mustache,
|
||||
program = block.program,
|
||||
accept: function(node) {
|
||||
this.sourceNode.unshift(node);
|
||||
let ret = this[node.type](node);
|
||||
this.sourceNode.shift();
|
||||
return ret;
|
||||
},
|
||||
|
||||
Program: function(program) {
|
||||
this.options.blockParams.unshift(program.blockParams);
|
||||
|
||||
let body = program.body,
|
||||
bodyLength = body.length;
|
||||
for (let i = 0; i < bodyLength; i++) {
|
||||
this.accept(body[i]);
|
||||
}
|
||||
|
||||
this.options.blockParams.shift();
|
||||
|
||||
this.isSimple = bodyLength === 1;
|
||||
this.blockParams = program.blockParams ? program.blockParams.length : 0;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
BlockStatement: function(block) {
|
||||
transformLiteralToPath(block);
|
||||
|
||||
let program = block.program,
|
||||
inverse = block.inverse;
|
||||
|
||||
if (program) {
|
||||
program = this.compileProgram(program);
|
||||
}
|
||||
program = program && this.compileProgram(program);
|
||||
inverse = inverse && this.compileProgram(inverse);
|
||||
|
||||
if (inverse) {
|
||||
inverse = this.compileProgram(inverse);
|
||||
}
|
||||
let type = this.classifySexpr(block);
|
||||
|
||||
var sexpr = mustache.sexpr;
|
||||
var type = this.classifySexpr(sexpr);
|
||||
|
||||
if (type === "helper") {
|
||||
this.helperSexpr(sexpr, program, inverse);
|
||||
} else if (type === "simple") {
|
||||
this.simpleSexpr(sexpr);
|
||||
if (type === 'helper') {
|
||||
this.helperSexpr(block, program, inverse);
|
||||
} else if (type === 'simple') {
|
||||
this.simpleSexpr(block);
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('blockValue', sexpr.id.original);
|
||||
this.opcode('blockValue', block.path.original);
|
||||
} else {
|
||||
this.ambiguousSexpr(sexpr, program, inverse);
|
||||
this.ambiguousSexpr(block, program, inverse);
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
@@ -187,177 +148,190 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
hash: function(hash) {
|
||||
var pairs = hash.pairs, i, l;
|
||||
|
||||
this.opcode('pushHash');
|
||||
|
||||
for(i=0, l=pairs.length; i<l; i++) {
|
||||
this.pushParam(pairs[i][1]);
|
||||
}
|
||||
while(i--) {
|
||||
this.opcode('assignToHash', pairs[i][0]);
|
||||
}
|
||||
this.opcode('popHash');
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var partialName = partial.partialName;
|
||||
PartialStatement: function(partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
if (partial.hash) {
|
||||
this.accept(partial.hash);
|
||||
} else {
|
||||
this.opcode('push', 'undefined');
|
||||
let params = partial.params;
|
||||
if (params.length > 1) {
|
||||
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
|
||||
} else if (!params.length) {
|
||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
||||
}
|
||||
|
||||
if (partial.context) {
|
||||
this.accept(partial.context);
|
||||
} else {
|
||||
this.opcode('push', 'depth0');
|
||||
let partialName = partial.name.original,
|
||||
isDynamic = partial.name.type === 'SubExpression';
|
||||
if (isDynamic) {
|
||||
this.accept(partial.name);
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', partialName.name);
|
||||
this.setupFullMustacheParams(partial, undefined, undefined, true);
|
||||
|
||||
let indent = partial.indent || '';
|
||||
if (this.options.preventIndent && indent) {
|
||||
this.opcode('appendContent', indent);
|
||||
indent = '';
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
content: function(content) {
|
||||
this.opcode('appendContent', content.string);
|
||||
},
|
||||
MustacheStatement: function(mustache) {
|
||||
this.SubExpression(mustache); // eslint-disable-line new-cap
|
||||
|
||||
mustache: function(mustache) {
|
||||
this.sexpr(mustache.sexpr);
|
||||
|
||||
if(mustache.escaped && !this.options.noEscape) {
|
||||
if (mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
} else {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
var id = sexpr.id,
|
||||
name = id.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
var id = sexpr.id;
|
||||
|
||||
if (id.type === 'DATA') {
|
||||
this.DATA(id);
|
||||
} else if (id.parts.length) {
|
||||
this.ID(id);
|
||||
} else {
|
||||
// Simplified ID for `this`
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('pushContext');
|
||||
}
|
||||
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
id = sexpr.id,
|
||||
name = id.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
||||
} else {
|
||||
this.ID(id);
|
||||
this.opcode('invokeHelper', params.length, id.original, sexpr.isRoot);
|
||||
ContentStatement: function(content) {
|
||||
if (content.value) {
|
||||
this.opcode('appendContent', content.value);
|
||||
}
|
||||
},
|
||||
|
||||
sexpr: function(sexpr) {
|
||||
var type = this.classifySexpr(sexpr);
|
||||
CommentStatement: function() {},
|
||||
|
||||
if (type === "simple") {
|
||||
SubExpression: function(sexpr) {
|
||||
transformLiteralToPath(sexpr);
|
||||
let type = this.classifySexpr(sexpr);
|
||||
|
||||
if (type === 'simple') {
|
||||
this.simpleSexpr(sexpr);
|
||||
} else if (type === "helper") {
|
||||
} else if (type === 'helper') {
|
||||
this.helperSexpr(sexpr);
|
||||
} else {
|
||||
this.ambiguousSexpr(sexpr);
|
||||
}
|
||||
},
|
||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||
let path = sexpr.path,
|
||||
name = path.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
var name = id.parts[0];
|
||||
if (!name) {
|
||||
this.opcode('pushContext');
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
this.accept(path);
|
||||
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
this.accept(sexpr.path);
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||
path = sexpr.path,
|
||||
name = path.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
|
||||
} else {
|
||||
this.opcode('lookupOnContext', id.parts[0]);
|
||||
}
|
||||
path.falsy = true;
|
||||
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
this.accept(path);
|
||||
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
|
||||
}
|
||||
},
|
||||
|
||||
DATA: function(data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', data.id.depth);
|
||||
var parts = data.id.parts;
|
||||
for(var i=0, l=parts.length; i<l; i++) {
|
||||
this.opcode('lookup', parts[i]);
|
||||
PathExpression: function(path) {
|
||||
this.addDepth(path.depth);
|
||||
this.opcode('getContext', path.depth);
|
||||
|
||||
let name = path.parts[0],
|
||||
scoped = AST.helpers.scopedId(path),
|
||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||
|
||||
if (blockParamId) {
|
||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
||||
} else if (!name) {
|
||||
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
||||
this.opcode('pushContext');
|
||||
} else if (path.data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', path.depth, path.parts);
|
||||
} else {
|
||||
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
|
||||
}
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
StringLiteral: function(string) {
|
||||
this.opcode('pushString', string.value);
|
||||
},
|
||||
|
||||
NUMBER: function(number) {
|
||||
this.opcode('pushLiteral', number.number);
|
||||
NumberLiteral: function(number) {
|
||||
this.opcode('pushLiteral', number.value);
|
||||
},
|
||||
|
||||
BOOLEAN: function(bool) {
|
||||
this.opcode('pushLiteral', bool.bool);
|
||||
BooleanLiteral: function(bool) {
|
||||
this.opcode('pushLiteral', bool.value);
|
||||
},
|
||||
|
||||
comment: function() {},
|
||||
UndefinedLiteral: function() {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
},
|
||||
|
||||
NullLiteral: function() {
|
||||
this.opcode('pushLiteral', 'null');
|
||||
},
|
||||
|
||||
Hash: function(hash) {
|
||||
let pairs = hash.pairs,
|
||||
i = 0,
|
||||
l = pairs.length;
|
||||
|
||||
this.opcode('pushHash');
|
||||
|
||||
for (; i < l; i++) {
|
||||
this.pushParam(pairs[i].value);
|
||||
}
|
||||
while (i--) {
|
||||
this.opcode('assignToHash', pairs[i].key);
|
||||
}
|
||||
this.opcode('popHash');
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
opcode: function(name) {
|
||||
this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
|
||||
},
|
||||
|
||||
declare: function(name, value) {
|
||||
this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
|
||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
if(depth === 0) { return; }
|
||||
|
||||
if(!this.depths[depth]) {
|
||||
this.depths[depth] = true;
|
||||
this.depths.list.push(depth);
|
||||
if (!depth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.useDepths = true;
|
||||
},
|
||||
|
||||
classifySexpr: function(sexpr) {
|
||||
var isHelper = sexpr.isHelper;
|
||||
var isEligible = sexpr.eligibleHelper;
|
||||
var options = this.options;
|
||||
let isSimple = AST.helpers.simpleId(sexpr.path);
|
||||
|
||||
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
let isEligible = !isBlockParam && (isHelper || isSimple);
|
||||
|
||||
// if ambiguous, we can possibly resolve the ambiguity now
|
||||
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
||||
if (isEligible && !isHelper) {
|
||||
var name = sexpr.id.parts[0];
|
||||
let name = sexpr.path.parts[0],
|
||||
options = this.options;
|
||||
|
||||
if (options.knownHelpers[name]) {
|
||||
isHelper = true;
|
||||
@@ -366,108 +340,174 @@ Compiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (isHelper) { return "helper"; }
|
||||
else if (isEligible) { return "ambiguous"; }
|
||||
else { return "simple"; }
|
||||
if (isHelper) {
|
||||
return 'helper';
|
||||
} else if (isEligible) {
|
||||
return 'ambiguous';
|
||||
} else {
|
||||
return 'simple';
|
||||
}
|
||||
},
|
||||
|
||||
pushParams: function(params) {
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
this.pushParam(params[i]);
|
||||
}
|
||||
},
|
||||
|
||||
pushParam: function(val) {
|
||||
let value = val.value != null ? val.value : val.original || '';
|
||||
|
||||
if (this.stringParams) {
|
||||
if(val.depth) {
|
||||
if (value.replace) {
|
||||
value = value
|
||||
.replace(/^(\.?\.\/)*/g, '')
|
||||
.replace(/\//g, '.');
|
||||
}
|
||||
|
||||
if (val.depth) {
|
||||
this.addDepth(val.depth);
|
||||
}
|
||||
this.opcode('getContext', val.depth || 0);
|
||||
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
||||
this.opcode('pushStringParam', value, val.type);
|
||||
|
||||
if (val.type === 'sexpr') {
|
||||
// Subexpressions get evaluated and passed in
|
||||
if (val.type === 'SubExpression') {
|
||||
// SubExpressions get evaluated and passed in
|
||||
// in string params mode.
|
||||
this.sexpr(val);
|
||||
this.accept(val);
|
||||
}
|
||||
} else {
|
||||
if (this.trackIds) {
|
||||
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
|
||||
let blockParamIndex;
|
||||
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
||||
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||
}
|
||||
if (blockParamIndex) {
|
||||
let blockParamChild = val.parts.slice(1).join('.');
|
||||
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
|
||||
} else {
|
||||
value = val.original || value;
|
||||
if (value.replace) {
|
||||
value = value
|
||||
.replace(/^\.\//g, '')
|
||||
.replace(/^\.$/g, '');
|
||||
}
|
||||
|
||||
this.opcode('pushId', val.type, value);
|
||||
}
|
||||
}
|
||||
this.accept(val);
|
||||
}
|
||||
},
|
||||
|
||||
setupFullMustacheParams: function(sexpr, program, inverse) {
|
||||
var params = sexpr.params;
|
||||
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
|
||||
let params = sexpr.params;
|
||||
this.pushParams(params);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
if (sexpr.hash) {
|
||||
this.hash(sexpr.hash);
|
||||
this.accept(sexpr.hash);
|
||||
} else {
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('emptyHash', omitEmpty);
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
blockParamIndex: function(name) {
|
||||
for (let depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
|
||||
let blockParams = this.options.blockParams[depth],
|
||||
param = blockParams && indexOf(blockParams, name);
|
||||
if (blockParams && param >= 0) {
|
||||
return [depth, param];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function precompile(input, options, env) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
|
||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
|
||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
var ast = env.parse(input);
|
||||
var environment = new env.Compiler().compile(ast, options);
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options);
|
||||
return new env.JavaScriptCompiler().compile(environment, options);
|
||||
}
|
||||
|
||||
export function compile(input, options, env) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
|
||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
||||
export function compile(input, options = {}, env) {
|
||||
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
|
||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
var compiled;
|
||||
let compiled;
|
||||
|
||||
function compileInput() {
|
||||
var ast = env.parse(input);
|
||||
var environment = new env.Compiler().compile(ast, options);
|
||||
var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
let ast = env.parse(input, options),
|
||||
environment = new env.Compiler().compile(ast, options),
|
||||
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
return env.template(templateSpec);
|
||||
}
|
||||
|
||||
// Template is only compiled on first use and cached after that point.
|
||||
var ret = function(context, options) {
|
||||
function ret(context, execOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled.call(this, context, options);
|
||||
return compiled.call(this, context, execOptions);
|
||||
}
|
||||
ret._setup = function(setupOptions) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._setup(setupOptions);
|
||||
};
|
||||
ret._setup = function(options) {
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._setup(options);
|
||||
};
|
||||
ret._child = function(i) {
|
||||
if (!compiled) {
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled._child(i);
|
||||
return compiled._child(i, data, blockParams, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
function argEquals(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isArray(a) && isArray(b) && a.length === b.length) {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!argEquals(a[i], b[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function transformLiteralToPath(sexpr) {
|
||||
if (!sexpr.path.parts) {
|
||||
let literal = sexpr.path;
|
||||
// Casting to string here to make false and 0 literal values play nicely with the rest
|
||||
// of the system.
|
||||
sexpr.path = new AST.PathExpression(false, 0, [literal.original + ''], literal.original + '', literal.loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
export function SourceLocation(source, locInfo) {
|
||||
this.source = source;
|
||||
this.start = {
|
||||
line: locInfo.first_line,
|
||||
column: locInfo.first_column
|
||||
};
|
||||
this.end = {
|
||||
line: locInfo.last_line,
|
||||
column: locInfo.last_column
|
||||
};
|
||||
}
|
||||
|
||||
export function id(token) {
|
||||
if (/^\[.*\]$/.test(token)) {
|
||||
return token.substr(1, token.length - 2);
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
export function stripFlags(open, close) {
|
||||
return {
|
||||
open: open.charAt(2) === '~',
|
||||
close: close.charAt(close.length - 3) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
export function stripComment(comment) {
|
||||
return comment.replace(/^\{\{~?\!-?-?/, '')
|
||||
.replace(/-?-?~?\}\}$/, '');
|
||||
}
|
||||
|
||||
export function preparePath(data, parts, locInfo) {
|
||||
locInfo = this.locInfo(locInfo);
|
||||
|
||||
let original = data ? '@' : '',
|
||||
dig = [],
|
||||
depth = 0,
|
||||
depthString = '';
|
||||
|
||||
for (let i = 0, l = parts.length; i < l; i++) {
|
||||
let part = parts[i].part,
|
||||
// If we have [] syntax then we do not treat path references as operators,
|
||||
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||
isLiteral = parts[i].original !== part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||
if (dig.length > 0) {
|
||||
throw new Exception('Invalid path: ' + original, {loc: locInfo});
|
||||
} else if (part === '..') {
|
||||
depth++;
|
||||
depthString += '../';
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
return new this.PathExpression(data, depth, dig, original, locInfo);
|
||||
}
|
||||
|
||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
// Must use charAt to support IE pre-10
|
||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
|
||||
}
|
||||
|
||||
export function prepareRawBlock(openRawBlock, content, close, locInfo) {
|
||||
if (openRawBlock.path.original !== close) {
|
||||
let errorNode = {loc: openRawBlock.path.loc};
|
||||
|
||||
throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
|
||||
}
|
||||
|
||||
locInfo = this.locInfo(locInfo);
|
||||
let program = new this.Program([content], null, {}, locInfo);
|
||||
|
||||
return new this.BlockStatement(
|
||||
openRawBlock.path, openRawBlock.params, openRawBlock.hash,
|
||||
program, undefined,
|
||||
{}, {}, {},
|
||||
locInfo);
|
||||
}
|
||||
|
||||
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
|
||||
// When we are chaining inverse calls, we will not have a close path
|
||||
if (close && close.path && openBlock.path.original !== close.path.original) {
|
||||
let errorNode = {loc: openBlock.path.loc};
|
||||
|
||||
throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
|
||||
}
|
||||
|
||||
program.blockParams = openBlock.blockParams;
|
||||
|
||||
let inverse,
|
||||
inverseStrip;
|
||||
|
||||
if (inverseAndProgram) {
|
||||
if (inverseAndProgram.chain) {
|
||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||
}
|
||||
|
||||
inverseStrip = inverseAndProgram.strip;
|
||||
inverse = inverseAndProgram.program;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
inverted = inverse;
|
||||
inverse = program;
|
||||
program = inverted;
|
||||
}
|
||||
|
||||
return new this.BlockStatement(
|
||||
openBlock.path, openBlock.params, openBlock.hash,
|
||||
program, inverse,
|
||||
openBlock.strip, inverseStrip, close && close.strip,
|
||||
this.locInfo(locInfo));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import Visitor from "./visitor";
|
||||
/*eslint-disable new-cap */
|
||||
import Visitor from './visitor';
|
||||
|
||||
export function print(ast) {
|
||||
return new PrintVisitor().accept(ast);
|
||||
@@ -10,26 +11,33 @@ export function PrintVisitor() {
|
||||
|
||||
PrintVisitor.prototype = new Visitor();
|
||||
|
||||
PrintVisitor.prototype.pad = function(string, newline) {
|
||||
var out = "";
|
||||
PrintVisitor.prototype.pad = function(string) {
|
||||
let out = '';
|
||||
|
||||
for(var i=0,l=this.padding; i<l; i++) {
|
||||
out = out + " ";
|
||||
for (let i = 0, l = this.padding; i < l; i++) {
|
||||
out = out + ' ';
|
||||
}
|
||||
|
||||
out = out + string;
|
||||
|
||||
if(newline !== false) { out = out + "\n"; }
|
||||
out = out + string + '\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.program = function(program) {
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
PrintVisitor.prototype.Program = function(program) {
|
||||
let out = '',
|
||||
body = program.body,
|
||||
i, l;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
out = out + this.accept(statements[i]);
|
||||
if (program.blockParams) {
|
||||
let blockParams = 'BLOCK PARAMS: [';
|
||||
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
||||
blockParams += ' ' + program.blockParams[i];
|
||||
}
|
||||
blockParams += ' ]';
|
||||
out += this.pad(blockParams);
|
||||
}
|
||||
|
||||
for (i = 0, l = body.length; i < l; i++) {
|
||||
out = out + this.accept(body[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
@@ -37,21 +45,25 @@ PrintVisitor.prototype.program = function(program) {
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.block = function(block) {
|
||||
var out = "";
|
||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
|
||||
out = out + this.pad("BLOCK:");
|
||||
PrintVisitor.prototype.BlockStatement = function(block) {
|
||||
let out = '';
|
||||
|
||||
out = out + this.pad('BLOCK:');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out = out + this.pad(this.SubExpression(block));
|
||||
if (block.program) {
|
||||
out = out + this.pad("PROGRAM:");
|
||||
out = out + this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad("{{^}}");
|
||||
out = out + this.pad('{{^}}');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.inverse);
|
||||
this.padding--;
|
||||
@@ -62,82 +74,78 @@ PrintVisitor.prototype.block = function(block) {
|
||||
return out;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.sexpr = function(sexpr) {
|
||||
var params = sexpr.params, paramStrings = [], hash;
|
||||
PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||
let content = 'PARTIAL:' + partial.name.original;
|
||||
if (partial.params[0]) {
|
||||
content += ' ' + this.accept(partial.params[0]);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += ' ' + this.accept(partial.hash);
|
||||
}
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
PrintVisitor.prototype.ContentStatement = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.value + "' ]");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.CommentStatement = function(comment) {
|
||||
return this.pad("{{! '" + comment.value + "' }}");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
||||
let params = sexpr.params,
|
||||
paramStrings = [],
|
||||
hash;
|
||||
|
||||
for (let i = 0, l = params.length; i < l; i++) {
|
||||
paramStrings.push(this.accept(params[i]));
|
||||
}
|
||||
|
||||
params = "[" + paramStrings.join(", ") + "]";
|
||||
params = '[' + paramStrings.join(', ') + ']';
|
||||
|
||||
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
|
||||
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
|
||||
|
||||
return this.accept(sexpr.id) + " " + params + hash;
|
||||
return this.accept(sexpr.path) + ' ' + params + hash;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.mustache = function(mustache) {
|
||||
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
|
||||
PrintVisitor.prototype.PathExpression = function(id) {
|
||||
let path = id.parts.join('/');
|
||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) {
|
||||
content += " " + this.accept(partial.context);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += " " + this.accept(partial.hash);
|
||||
}
|
||||
return this.pad("{{> " + content + " }}");
|
||||
|
||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||
return '"' + string.value + '"';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.hash = function(hash) {
|
||||
var pairs = hash.pairs;
|
||||
var joinedPairs = [], left, right;
|
||||
PrintVisitor.prototype.NumberLiteral = function(number) {
|
||||
return 'NUMBER{' + number.value + '}';
|
||||
};
|
||||
|
||||
for(var i=0, l=pairs.length; i<l; i++) {
|
||||
left = pairs[i][0];
|
||||
right = this.accept(pairs[i][1]);
|
||||
joinedPairs.push( left + "=" + right );
|
||||
PrintVisitor.prototype.BooleanLiteral = function(bool) {
|
||||
return 'BOOLEAN{' + bool.value + '}';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.UndefinedLiteral = function() {
|
||||
return 'UNDEFINED';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.NullLiteral = function() {
|
||||
return 'NULL';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.Hash = function(hash) {
|
||||
let pairs = hash.pairs,
|
||||
joinedPairs = [];
|
||||
|
||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||
joinedPairs.push(this.accept(pairs[i]));
|
||||
}
|
||||
|
||||
return "HASH{" + joinedPairs.join(", ") + "}";
|
||||
return 'HASH{' + joinedPairs.join(', ') + '}';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.STRING = function(string) {
|
||||
return '"' + string.string + '"';
|
||||
PrintVisitor.prototype.HashPair = function(pair) {
|
||||
return pair.key + '=' + this.accept(pair.value);
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.NUMBER = function(number) {
|
||||
return "NUMBER{" + number.number + "}";
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
return "BOOLEAN{" + bool.bool + "}";
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.ID = function(id) {
|
||||
var path = id.parts.join("/");
|
||||
if(id.parts.length > 1) {
|
||||
return "PATH:" + path;
|
||||
} else {
|
||||
return "ID:" + path;
|
||||
}
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
return "PARTIAL:" + partialName.name;
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + this.accept(data.id);
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.comment = function(comment) {
|
||||
return this.pad("{{! '" + comment.comment + "' }}");
|
||||
};
|
||||
|
||||
/*eslint-enable new-cap */
|
||||
|
||||
@@ -1,10 +1,118 @@
|
||||
function Visitor() {}
|
||||
import Exception from '../exception';
|
||||
import AST from './ast';
|
||||
|
||||
function Visitor() {
|
||||
this.parents = [];
|
||||
}
|
||||
|
||||
Visitor.prototype = {
|
||||
constructor: Visitor,
|
||||
mutating: false,
|
||||
|
||||
// Visits a given value. If mutating, will replace the value if necessary.
|
||||
acceptKey: function(node, name) {
|
||||
let value = this.accept(node[name]);
|
||||
if (this.mutating) {
|
||||
// Hacky sanity check:
|
||||
if (value && (!value.type || !AST[value.type])) {
|
||||
throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
|
||||
}
|
||||
node[name] = value;
|
||||
}
|
||||
},
|
||||
|
||||
// Performs an accept operation with added sanity check to ensure
|
||||
// required keys are not removed.
|
||||
acceptRequired: function(node, name) {
|
||||
this.acceptKey(node, name);
|
||||
|
||||
if (!node[name]) {
|
||||
throw new Exception(node.type + ' requires ' + name);
|
||||
}
|
||||
},
|
||||
|
||||
// Traverses a given array. If mutating, empty respnses will be removed
|
||||
// for child elements.
|
||||
acceptArray: function(array) {
|
||||
for (let i = 0, l = array.length; i < l; i++) {
|
||||
this.acceptKey(array, i);
|
||||
|
||||
if (!array[i]) {
|
||||
array.splice(i, 1);
|
||||
i--;
|
||||
l--;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
accept: function(object) {
|
||||
return this[object.type](object);
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current) {
|
||||
this.parents.unshift(this.current);
|
||||
}
|
||||
this.current = object;
|
||||
|
||||
let ret = this[object.type](object);
|
||||
|
||||
this.current = this.parents.shift();
|
||||
|
||||
if (!this.mutating || ret) {
|
||||
return ret;
|
||||
} else if (ret !== false) {
|
||||
return object;
|
||||
}
|
||||
},
|
||||
|
||||
Program: function(program) {
|
||||
this.acceptArray(program.body);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
},
|
||||
|
||||
BlockStatement: function(block) {
|
||||
this.acceptRequired(block, 'path');
|
||||
this.acceptArray(block.params);
|
||||
this.acceptKey(block, 'hash');
|
||||
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
},
|
||||
|
||||
ContentStatement: function(/* content */) {},
|
||||
CommentStatement: function(/* comment */) {},
|
||||
|
||||
SubExpression: function(sexpr) {
|
||||
this.acceptRequired(sexpr, 'path');
|
||||
this.acceptArray(sexpr.params);
|
||||
this.acceptKey(sexpr, 'hash');
|
||||
},
|
||||
|
||||
PathExpression: function(/* path */) {},
|
||||
|
||||
StringLiteral: function(/* string */) {},
|
||||
NumberLiteral: function(/* number */) {},
|
||||
BooleanLiteral: function(/* bool */) {},
|
||||
UndefinedLiteral: function(/* literal */) {},
|
||||
NullLiteral: function(/* literal */) {},
|
||||
|
||||
Hash: function(hash) {
|
||||
this.acceptArray(hash.pairs);
|
||||
},
|
||||
HashPair: function(pair) {
|
||||
this.acceptRequired(pair, 'value');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import Visitor from './visitor';
|
||||
|
||||
function WhitespaceControl() {
|
||||
}
|
||||
WhitespaceControl.prototype = new Visitor();
|
||||
|
||||
WhitespaceControl.prototype.Program = function(program) {
|
||||
let isRoot = !this.isRootSeen;
|
||||
this.isRootSeen = true;
|
||||
|
||||
let body = program.body;
|
||||
for (let i = 0, l = body.length; i < l; i++) {
|
||||
let current = body[i],
|
||||
strip = this.accept(current);
|
||||
|
||||
if (!strip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
||||
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
|
||||
if (strip.close) {
|
||||
omitRight(body, i, true);
|
||||
}
|
||||
if (strip.open) {
|
||||
omitLeft(body, i, true);
|
||||
}
|
||||
|
||||
if (inlineStandalone) {
|
||||
omitRight(body, i);
|
||||
|
||||
if (omitLeft(body, i)) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if (current.type === 'PartialStatement') {
|
||||
// Pull out the whitespace from the final line
|
||||
current.indent = (/([ \t]+$)/).exec(body[i - 1].original)[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openStandalone) {
|
||||
omitRight((current.program || current.inverse).body);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i);
|
||||
}
|
||||
if (closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(body, i);
|
||||
|
||||
omitLeft((current.inverse || current.program).body);
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
};
|
||||
WhitespaceControl.prototype.BlockStatement = function(block) {
|
||||
this.accept(block.program);
|
||||
this.accept(block.inverse);
|
||||
|
||||
// Find the inverse program that is involed with whitespace stripping.
|
||||
let program = block.program || block.inverse,
|
||||
inverse = block.program && block.inverse,
|
||||
firstInverse = inverse,
|
||||
lastInverse = inverse;
|
||||
|
||||
if (inverse && inverse.chained) {
|
||||
firstInverse = inverse.body[0].program;
|
||||
|
||||
// Walk the inverse chain to find the last inverse that is actually in the chain.
|
||||
while (lastInverse.chained) {
|
||||
lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
|
||||
}
|
||||
}
|
||||
|
||||
let strip = {
|
||||
open: block.openStrip.open,
|
||||
close: block.closeStrip.close,
|
||||
|
||||
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
||||
// so our parent can determine if we actually are standalone
|
||||
openStandalone: isNextWhitespace(program.body),
|
||||
closeStandalone: isPrevWhitespace((firstInverse || program).body)
|
||||
};
|
||||
|
||||
if (block.openStrip.close) {
|
||||
omitRight(program.body, null, true);
|
||||
}
|
||||
|
||||
if (inverse) {
|
||||
let inverseStrip = block.inverseStrip;
|
||||
|
||||
if (inverseStrip.open) {
|
||||
omitLeft(program.body, null, true);
|
||||
}
|
||||
|
||||
if (inverseStrip.close) {
|
||||
omitRight(firstInverse.body, null, true);
|
||||
}
|
||||
if (block.closeStrip.open) {
|
||||
omitLeft(lastInverse.body, null, true);
|
||||
}
|
||||
|
||||
// Find standalone else statments
|
||||
if (isPrevWhitespace(program.body)
|
||||
&& isNextWhitespace(firstInverse.body)) {
|
||||
omitLeft(program.body);
|
||||
omitRight(firstInverse.body);
|
||||
}
|
||||
} else if (block.closeStrip.open) {
|
||||
omitLeft(program.body, null, true);
|
||||
}
|
||||
|
||||
return strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
|
||||
return mustache.strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.PartialStatement =
|
||||
WhitespaceControl.prototype.CommentStatement = function(node) {
|
||||
/* istanbul ignore next */
|
||||
let strip = node.strip || {};
|
||||
return {
|
||||
inlineStandalone: true,
|
||||
open: strip.open,
|
||||
close: strip.close
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function isPrevWhitespace(body, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = body.length;
|
||||
}
|
||||
|
||||
// Nodes that end with newlines are considered whitespace (but are special
|
||||
// cased for strip operations)
|
||||
let prev = body[i - 1],
|
||||
sibling = body[i - 2];
|
||||
if (!prev) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (prev.type === 'ContentStatement') {
|
||||
return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
|
||||
}
|
||||
}
|
||||
function isNextWhitespace(body, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = -1;
|
||||
}
|
||||
|
||||
let next = body[i + 1],
|
||||
sibling = body[i + 2];
|
||||
if (!next) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (next.type === 'ContentStatement') {
|
||||
return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
|
||||
}
|
||||
}
|
||||
|
||||
// Marks the node to the right of the position as omitted.
|
||||
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined, then the first child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitRight(body, i, multiple) {
|
||||
let current = body[i == null ? 0 : i + 1];
|
||||
if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let original = current.value;
|
||||
current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
|
||||
current.rightStripped = current.value !== original;
|
||||
}
|
||||
|
||||
// Marks the node to the left of the position as omitted.
|
||||
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined then the last child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitLeft(body, i, multiple) {
|
||||
let current = body[i == null ? body.length - 1 : i - 1];
|
||||
if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We omit the last node if it's whitespace only and not preceeded by a non-content node.
|
||||
let original = current.value;
|
||||
current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
|
||||
current.leftStripped = current.value !== original;
|
||||
return current.leftStripped;
|
||||
}
|
||||
|
||||
export default WhitespaceControl;
|
||||
@@ -1,24 +1,31 @@
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
var line;
|
||||
if (node && node.firstLine) {
|
||||
line = node.firstLine;
|
||||
let loc = node && node.loc,
|
||||
line,
|
||||
column;
|
||||
if (loc) {
|
||||
line = loc.start.line;
|
||||
column = loc.start.column;
|
||||
|
||||
message += ' - ' + line + ':' + node.firstColumn;
|
||||
message += ' - ' + line + ':' + column;
|
||||
}
|
||||
|
||||
var tmp = Error.prototype.constructor.call(this, message);
|
||||
let tmp = Error.prototype.constructor.call(this, message);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
for (let idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
if (line) {
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, Exception);
|
||||
}
|
||||
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
this.column = node.firstColumn;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*global window */
|
||||
export default function(Handlebars) {
|
||||
/* istanbul ignore next */
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
$Handlebars = root.Handlebars;
|
||||
/* istanbul ignore next */
|
||||
Handlebars.noConflict = function() {
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
}
|
||||
};
|
||||
}
|
||||
+117
-73
@@ -1,21 +1,21 @@
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base";
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
||||
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,33 +23,68 @@ export function checkRevision(compilerInfo) {
|
||||
// TODO: Remove this line and break up compilePartial
|
||||
|
||||
export function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
if (!env) {
|
||||
throw new Exception("No environment passed to template");
|
||||
throw new Exception('No environment passed to template');
|
||||
}
|
||||
if (!templateSpec || !templateSpec.main) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
|
||||
if (hash) {
|
||||
context = Utils.extend({}, context, hash);
|
||||
function invokePartialWrapper(partial, context, options) {
|
||||
if (options.hash) {
|
||||
context = Utils.extend({}, context, options.hash);
|
||||
}
|
||||
|
||||
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
|
||||
if (result != null) { return result; }
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||
|
||||
if (env.compile) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
partials[name] = env.compile(partial, { data: data !== undefined }, env);
|
||||
return partials[name](context, options);
|
||||
if (result == null && env.compile) {
|
||||
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
||||
result = options.partials[options.name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
if (options.indent) {
|
||||
let lines = result.split('\n');
|
||||
for (let i = 0, l = lines.length; i < l; i++) {
|
||||
if (!lines[i] && i + 1 === l) {
|
||||
break;
|
||||
}
|
||||
|
||||
lines[i] = options.indent + lines[i];
|
||||
}
|
||||
result = lines.join('\n');
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
let container = {
|
||||
strict: function(obj, name) {
|
||||
if (!(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
||||
}
|
||||
return obj[name];
|
||||
},
|
||||
lookup: function(depths, name) {
|
||||
const len = depths.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
@@ -58,50 +93,53 @@ export function template(templateSpec, env) {
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data) {
|
||||
var programWrapper = this.programs[i],
|
||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||
let programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if(data) {
|
||||
programWrapper = program(this, i, fn, data);
|
||||
if (data || depths || blockParams || declaredBlockParams) {
|
||||
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = program(this, i, fn);
|
||||
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
programWithDepth: env.VM.programWithDepth,
|
||||
|
||||
data: function(data, depth) {
|
||||
while (data && depth--) {
|
||||
data = data._parent;
|
||||
data: function(value, depth) {
|
||||
while (value && depth--) {
|
||||
value = value._parent;
|
||||
}
|
||||
return data;
|
||||
return value;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
let obj = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
ret = Utils.extend({}, common, param);
|
||||
obj = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return obj;
|
||||
},
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
};
|
||||
|
||||
var ret = function(context, options) {
|
||||
options = options || {};
|
||||
var helpers,
|
||||
partials,
|
||||
data = options.data;
|
||||
function ret(context, options = {}) {
|
||||
let data = options.data;
|
||||
|
||||
ret._setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data);
|
||||
};
|
||||
let depths,
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
if (templateSpec.useDepths) {
|
||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
||||
}
|
||||
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
}
|
||||
ret.isTop = true;
|
||||
|
||||
ret._setup = function(options) {
|
||||
if (!options.partial) {
|
||||
@@ -116,50 +154,56 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
};
|
||||
|
||||
ret._child = function(i) {
|
||||
return container.programWithDepth(i);
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (templateSpec.useBlockParams && !blockParams) {
|
||||
throw new Exception('must pass block params');
|
||||
}
|
||||
if (templateSpec.useDepths && !depths) {
|
||||
throw new Exception('must pass parent depths');
|
||||
}
|
||||
|
||||
return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function programWithDepth(i, data /*, $depth */) {
|
||||
/*jshint -W040 */
|
||||
var args = Array.prototype.slice.call(arguments, 2),
|
||||
container = this,
|
||||
fn = container.fn(i);
|
||||
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
|
||||
};
|
||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
||||
function prog(context, options = {}) {
|
||||
return fn.call(container,
|
||||
context,
|
||||
container.helpers, container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
depths && [context].concat(depths));
|
||||
}
|
||||
prog.program = i;
|
||||
prog.depth = args.length;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
prog.blockParams = declaredBlockParams || 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
export function program(container, i, fn, data) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.call(container, context, container.helpers, container.partials, options.data || data);
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = 0;
|
||||
return prog;
|
||||
export function resolvePartial(partial, context, options) {
|
||||
if (!partial) {
|
||||
partial = options.partials[options.name];
|
||||
} else if (!partial.call && !options.name) {
|
||||
// This is a dynamic partial that returned a string
|
||||
options.name = partial;
|
||||
partial = options.partials[partial];
|
||||
}
|
||||
return partial;
|
||||
}
|
||||
|
||||
export function invokePartial(partial, name, context, helpers, partials, data) {
|
||||
var options = { partial: true, helpers: helpers, partials: partials, data: data };
|
||||
export function invokePartial(partial, context, options) {
|
||||
options.partial = true;
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
if (partial === undefined) {
|
||||
throw new Exception('The partial ' + options.name + ' could not be found');
|
||||
} else if (partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() { return ""; }
|
||||
export function noop() { return ''; }
|
||||
|
||||
function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
|
||||
@@ -3,8 +3,8 @@ function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = function() {
|
||||
return "" + this.string;
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
||||
return '' + this.string;
|
||||
};
|
||||
|
||||
export default SafeString;
|
||||
|
||||
+48
-28
@@ -1,25 +1,22 @@
|
||||
/*jshint -W004 */
|
||||
import SafeString from "./safe-string";
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
const escape = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`'
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
const badChars = /[&<>"'`]/g,
|
||||
possible = /[&<>"'`]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr] || "&";
|
||||
return escape[chr];
|
||||
}
|
||||
|
||||
export function extend(obj /* , ...source */) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
for (var key in arguments[i]) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
for (let key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
obj[key] = arguments[i][key];
|
||||
}
|
||||
@@ -29,40 +26,58 @@ export function extend(obj /* , ...source */) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
export var toString = Object.prototype.toString;
|
||||
export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/*eslint-disable func-style, no-var */
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export var isFunction;
|
||||
/*eslint-enable func-style, no-var */
|
||||
|
||||
export var isArray = Array.isArray || function(value) {
|
||||
/* istanbul ignore next */
|
||||
export const isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
|
||||
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||
export function indexOf(array, value) {
|
||||
for (let i = 0, len = array.length; i < len; i++) {
|
||||
if (array[i] === value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
export function escapeExpression(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof SafeString) {
|
||||
return string.toString();
|
||||
} else if (!string && string !== 0) {
|
||||
return "";
|
||||
if (typeof string !== 'string') {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string && string.toHTML) {
|
||||
return string.toHTML();
|
||||
} else if (string == null) {
|
||||
return '';
|
||||
} else if (!string) {
|
||||
return string + '';
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = '' + string;
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = "" + string;
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
if (!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
@@ -76,6 +91,11 @@ export function isEmpty(value) {
|
||||
}
|
||||
}
|
||||
|
||||
export function blockParams(params, ids) {
|
||||
params.path = ids;
|
||||
return params;
|
||||
}
|
||||
|
||||
export function appendContextPath(contextPath, id) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,11 +1,10 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-disable no-var */
|
||||
|
||||
// var local = handlebars.create();
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars')["default"];
|
||||
|
||||
handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor')["default"];
|
||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||
|
||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||
@@ -14,12 +13,13 @@ handlebars.print = printer.print;
|
||||
module.exports = handlebars;
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
var extension = function(module, filename) {
|
||||
var fs = require("fs");
|
||||
var templateString = fs.readFileSync(filename, "utf8");
|
||||
module.exports = handlebars.compile(templateString);
|
||||
};
|
||||
require.extensions[".handlebars"] = extension;
|
||||
require.extensions[".hbs"] = extension;
|
||||
function extension(module, filename) {
|
||||
var fs = require('fs');
|
||||
var templateString = fs.readFileSync(filename, 'utf8');
|
||||
module.exports = handlebars.compile(templateString);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
require.extensions['.handlebars'] = extension;
|
||||
require.extensions['.hbs'] = extension;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*eslint-disable no-console */
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception(`Unable to open template file "${template}"`);
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||
}
|
||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
}
|
||||
|
||||
// Convert the known list into a hash
|
||||
let known = {};
|
||||
if (opts.known && !Array.isArray(opts.known)) {
|
||||
opts.known = [opts.known];
|
||||
}
|
||||
if (opts.known) {
|
||||
for (let i = 0, len = opts.known.length; i < len; i++) {
|
||||
known[opts.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
let extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
output.add(' var template = Handlebars.template, templates = ');
|
||||
if (opts.namespace) {
|
||||
output.add(opts.namespace);
|
||||
output.add(' = ');
|
||||
output.add(opts.namespace);
|
||||
output.add(' || ');
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
let path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
let childPath = template + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
processTemplate(childPath, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length + 1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
let precompiled = Handlebars.precompile(data, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
processTemplate(template, opts.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if (opts.partial) {
|
||||
output.add('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.add('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.add('})();');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (opts.map) {
|
||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
||||
}
|
||||
|
||||
output = output.toStringWithSourceMap();
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = uglify.minify(output.code, {
|
||||
fromString: true,
|
||||
|
||||
outSourceMap: opts.map,
|
||||
inSourceMap: JSON.parse(output.map)
|
||||
});
|
||||
if (opts.map) {
|
||||
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
fs.writeFileSync(opts.map, output.map, 'utf8');
|
||||
}
|
||||
output = output.code;
|
||||
|
||||
if (opts.output) {
|
||||
fs.writeFileSync(opts.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
+32
-18
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "2.0.0-alpha.4",
|
||||
"version": "3.0.3",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
@@ -21,35 +21,40 @@
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3"
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.1.40"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "~2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "~0.2.9",
|
||||
"async": "^0.9.0",
|
||||
"aws-sdk": "~1.5.0",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
"dustjs-linkedin": "~2.0.2",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-cli": "~0.1.10",
|
||||
"grunt-contrib-clean": "~0.4.1",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-jshint": "0.x",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.2",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-saucelabs": "~5.0.1",
|
||||
"es6-module-packager": "1.x",
|
||||
"grunt-contrib-clean": "0.x",
|
||||
"grunt-contrib-concat": "0.x",
|
||||
"grunt-contrib-connect": "0.x",
|
||||
"grunt-contrib-copy": "0.x",
|
||||
"grunt-contrib-requirejs": "0.x",
|
||||
"grunt-contrib-uglify": "0.x",
|
||||
"grunt-contrib-watch": "0.x",
|
||||
"grunt-eslint": "^11.0.0",
|
||||
"grunt-saucelabs": "8.x",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"keen.io": "0.0.3",
|
||||
"mocha": "*",
|
||||
"mustache": "~0.7.2",
|
||||
"semver": "~2.1.0",
|
||||
"underscore": "~1.5.1"
|
||||
"mocha": "~1.20.0",
|
||||
"mustache": "0.x",
|
||||
"semver": "^4.0.0",
|
||||
"underscore": "^1.5.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
@@ -57,5 +62,14 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/amd"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+126
-1
@@ -2,7 +2,132 @@
|
||||
|
||||
## Development
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...master)
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...master)
|
||||
|
||||
## 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://api.github.com/users/boskee))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.2...v3.0.3)
|
||||
|
||||
## v3.0.2 - April 20th, 2015
|
||||
- [#998](https://github.com/wycats/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#994](https://github.com/wycats/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://api.github.com/users/tamlyn))
|
||||
- [#990](https://github.com/wycats/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://api.github.com/users/blimmer))
|
||||
- [#989](https://github.com/wycats/handlebars.js/issues/989) - Source-map error with requirejs ([@SteppeEagle](https://api.github.com/users/SteppeEagle))
|
||||
- [#967](https://github.com/wycats/handlebars.js/issues/967) - can't access "this" property ([@75lb](https://api.github.com/users/75lb))
|
||||
- Use captureStackTrace for error handler - a009a97
|
||||
- Ignore branches tested without coverage monitoring - 37a664b
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.1...v3.0.2)
|
||||
|
||||
## v3.0.1 - March 24th, 2015
|
||||
- [#984](https://github.com/wycats/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://api.github.com/users/johneke))
|
||||
- [#973](https://github.com/wycats/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://api.github.com/users/elover))
|
||||
- [#966](https://github.com/wycats/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://api.github.com/users/abloomston))
|
||||
- [#964](https://github.com/wycats/handlebars.js/pull/964) - default is a reserved word ([@grassick](https://api.github.com/users/grassick))
|
||||
- [#962](https://github.com/wycats/handlebars.js/pull/962) - Add dashbars' link on README. ([@pismute](https://api.github.com/users/pismute))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.0...v3.0.1)
|
||||
|
||||
## v3.0.0 - February 10th, 2015
|
||||
- [#941](https://github.com/wycats/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#940](https://github.com/wycats/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://api.github.com/users/mattflaschen))
|
||||
- [#938](https://github.com/wycats/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://api.github.com/users/diwo))
|
||||
- [#930](https://github.com/wycats/handlebars.js/pull/930) - Add parent tracking and mutation to AST visitors ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#926](https://github.com/wycats/handlebars.js/issues/926) - Depthed lookups fail when program duplicator runs ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#918](https://github.com/wycats/handlebars.js/pull/918) - Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos ([@oneeman](https://api.github.com/users/oneeman))
|
||||
- [#915](https://github.com/wycats/handlebars.js/pull/915) - Ast update ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#910](https://github.com/wycats/handlebars.js/issues/910) - Different behavior of {{@last}} when {{#each}} in {{#each}} ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#907](https://github.com/wycats/handlebars.js/issues/907) - Implement named helper variable references ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#906](https://github.com/wycats/handlebars.js/pull/906) - Add parser support for block params ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#903](https://github.com/wycats/handlebars.js/issues/903) - Only provide aliases for multiple use calls ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#902](https://github.com/wycats/handlebars.js/pull/902) - Generate Source Maps ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#901](https://github.com/wycats/handlebars.js/issues/901) - Still escapes with noEscape enabled on isolated Handlebars environment ([@zedknight](https://api.github.com/users/zedknight))
|
||||
- [#896](https://github.com/wycats/handlebars.js/pull/896) - Simplify BlockNode by removing intermediate MustacheNode ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#892](https://github.com/wycats/handlebars.js/pull/892) - Implement parser for else chaining of helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#889](https://github.com/wycats/handlebars.js/issues/889) - Consider extensible parser API ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#887](https://github.com/wycats/handlebars.js/issues/887) - Handlebars.noConflict() option? ([@bradvogel](https://api.github.com/users/bradvogel))
|
||||
- [#886](https://github.com/wycats/handlebars.js/issues/886) - Add SafeString to context (or use duck-typing) ([@dominicbarnes](https://api.github.com/users/dominicbarnes))
|
||||
- [#870](https://github.com/wycats/handlebars.js/pull/870) - Registering undefined partial throws exception. ([@max-b](https://api.github.com/users/max-b))
|
||||
- [#866](https://github.com/wycats/handlebars.js/issues/866) - comments don't respect whitespace control ([@75lb](https://api.github.com/users/75lb))
|
||||
- [#863](https://github.com/wycats/handlebars.js/pull/863) - + jsDelivr CDN info ([@tomByrer](https://api.github.com/users/tomByrer))
|
||||
- [#858](https://github.com/wycats/handlebars.js/issues/858) - Disable new default auto-indent at included partials ([@majodev](https://api.github.com/users/majodev))
|
||||
- [#856](https://github.com/wycats/handlebars.js/pull/856) - jspm compatibility ([@MajorBreakfast](https://api.github.com/users/MajorBreakfast))
|
||||
- [#805](https://github.com/wycats/handlebars.js/issues/805) - Request: "strict" lookups ([@nzakas](https://api.github.com/users/nzakas))
|
||||
|
||||
- Export the default object for handlebars/runtime - 5594416
|
||||
- Lookup partials when undefined - 617dd57
|
||||
|
||||
Compatibility notes:
|
||||
- Runtime breaking changes. Must match 3.x runtime and precompiler.
|
||||
- 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
|
||||
- The Visitor API has been expanded to support mutation and provide a base implementation
|
||||
- The `JavaScriptCompiler` APIs have been formalized and documented. As part of the sourcemap handling these should be updated to return arrays for concatenation.
|
||||
- `JavaScriptCompiler.namespace` has been removed as it was unused.
|
||||
- `SafeString` is now duck typed on `toHTML`
|
||||
|
||||
New Features:
|
||||
- noConflict
|
||||
- Source Maps
|
||||
- Block Params
|
||||
- Strict Mode
|
||||
- @last and other each changes
|
||||
- Chained else blocks
|
||||
- @data methods can now have helper parameters passed to them
|
||||
- Dynamic partials
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...v3.0.0)
|
||||
|
||||
## v2.0.0 - September 1st, 2014
|
||||
- Update jsfiddle to 2.0.0-beta.1 - 0670f65
|
||||
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c
|
||||
- Play nice with gemspec version numbers - 64d5481
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
|
||||
|
||||
## v2.0.0-beta.1 - August 26th, 2014
|
||||
- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://api.github.com/users/scoot557))
|
||||
- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://api.github.com/users/evensoul))
|
||||
- Changes to 0/undefined handling
|
||||
- [#731](https://github.com/wycats/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#820](https://github.com/wycats/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#837](https://github.com/wycats/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#819](https://github.com/wycats/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#764](https://github.com/wycats/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#773](https://github.com/wycats/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention ([@Bertrand](https://api.github.com/users/Bertrand))
|
||||
- [#783](https://github.com/wycats/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://api.github.com/users/ErisDS))
|
||||
- [#795](https://github.com/wycats/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://api.github.com/users/jwietelmann))
|
||||
- [#823](https://github.com/wycats/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://api.github.com/users/dan-manges))
|
||||
- [#834](https://github.com/wycats/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#852](https://github.com/wycats/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#835](https://github.com/wycats/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
|
||||
|
||||
- Expose escapeExpression on the root object - 980c38c
|
||||
- Remove nested function eval in blockHelperMissing - 6f22ec1
|
||||
- Fix compiler program de-duping - 9e3f824
|
||||
|
||||
Compatibility notes:
|
||||
- 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.
|
||||
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
|
||||
- `false` values are now printed to output rather than silently dropped
|
||||
- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
|
||||
- Partials that are standalone will now indent their rendered content
|
||||
- `AST.ProgramNode`'s signature has changed.
|
||||
- Numerious methods/features removed from psuedo-API classes
|
||||
- `JavaScriptCompiler.register`
|
||||
- `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
|
||||
- `Compiler.disassemble`
|
||||
- `DECLARE` opcode
|
||||
- `strip` opcode
|
||||
- `lookup` opcode
|
||||
- Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
|
||||
- Removed unused `Handlebars.registerHelper` `inverse` parameter
|
||||
- `each` helper requires iterator parameter
|
||||
|
||||
[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
|
||||
- Expose setup wrappers for compiled templates - 3638874
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime');
|
||||
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"globals": {
|
||||
"CompilerContext": true,
|
||||
"Handlebars": true,
|
||||
"handlebarsEnv": true,
|
||||
|
||||
"shouldCompileTo": true,
|
||||
"shouldCompileToWithPartials": true,
|
||||
"shouldThrow": true,
|
||||
"compileWithPartials": true,
|
||||
|
||||
"console": true,
|
||||
"require": true,
|
||||
"suite": true,
|
||||
"equal": true,
|
||||
"equals": true,
|
||||
"test": true,
|
||||
"testBoth": true,
|
||||
"raises": true,
|
||||
"deepEqual": true,
|
||||
"start": true,
|
||||
"stop": true,
|
||||
"ok": true,
|
||||
"strictEqual": true,
|
||||
"define": true
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars['default'].template, true);
|
||||
equal(!!Handlebars['default'].VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
// Inject any require initilizer failures into the first test so they are properly
|
||||
// reported.
|
||||
if (requireFailure) {
|
||||
runner.on('hook end', function(hook){
|
||||
if (requireFailure) {
|
||||
runner.uncaught(requireFailure);
|
||||
requireFailure = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+19
-4
@@ -28,12 +28,17 @@
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars.amd',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
@@ -79,16 +84,26 @@
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
// Inject any require initilizer failures into the first test so they are properly
|
||||
// reported.
|
||||
if (requireFailure) {
|
||||
runner.on('hook end', function(hook){
|
||||
if (requireFailure) {
|
||||
runner.uncaught(requireFailure);
|
||||
requireFailure = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var flattenTitles = function(test){
|
||||
function logFailure(test, err){
|
||||
function flattenTitles(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
}
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
@@ -97,7 +112,7 @@
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
+209
-196
@@ -1,276 +1,289 @@
|
||||
/*global Handlebars, handlebarsEnv, shouldThrow */
|
||||
describe('ast', function() {
|
||||
if (!Handlebars.AST) {
|
||||
return;
|
||||
}
|
||||
|
||||
var LOCATION_INFO = {
|
||||
last_line: 0,
|
||||
first_line: 0,
|
||||
first_column: 0,
|
||||
last_column: 0
|
||||
start: {
|
||||
line: 1,
|
||||
column: 1
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 1
|
||||
}
|
||||
};
|
||||
|
||||
function testLocationInfoStorage(node){
|
||||
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
|
||||
property,
|
||||
propertiesLen = properties.length,
|
||||
i;
|
||||
|
||||
for (i = 0; i < propertiesLen; i++){
|
||||
property = properties[0];
|
||||
equals(node[property], 0);
|
||||
}
|
||||
function testLocationInfoStorage(node) {
|
||||
equals(node.loc.start.line, 1);
|
||||
equals(node.loc.start.column, 1);
|
||||
equals(node.loc.end.line, 1);
|
||||
equals(node.loc.end.column, 1);
|
||||
}
|
||||
|
||||
describe('MustacheNode', function() {
|
||||
function testEscape(open, expected) {
|
||||
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
|
||||
equals(mustache.escaped, expected);
|
||||
}
|
||||
|
||||
describe('MustacheStatement', function() {
|
||||
it('should store args', function() {
|
||||
var id = {isSimple: true},
|
||||
hash = {},
|
||||
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false, LOCATION_INFO);
|
||||
equals(mustache.type, 'mustache');
|
||||
equals(mustache.hash, hash);
|
||||
var mustache = new handlebarsEnv.AST.MustacheStatement({}, null, null, true, {}, LOCATION_INFO);
|
||||
equals(mustache.type, 'MustacheStatement');
|
||||
equals(mustache.escaped, true);
|
||||
equals(mustache.id, id);
|
||||
equals(mustache.params.length, 1);
|
||||
equals(mustache.params[0], 'param1');
|
||||
equals(!!mustache.isHelper, true);
|
||||
testLocationInfoStorage(mustache);
|
||||
});
|
||||
it('should accept token for escape', function() {
|
||||
testEscape('{{', true);
|
||||
testEscape('{{~', true);
|
||||
testEscape('{{#', true);
|
||||
testEscape('{{~#', true);
|
||||
testEscape('{{/', true);
|
||||
testEscape('{{~/', true);
|
||||
testEscape('{{^', true);
|
||||
testEscape('{{~^', true);
|
||||
testEscape('{', true);
|
||||
testEscape('{', true);
|
||||
|
||||
testEscape('{{&', false);
|
||||
testEscape('{{~&', false);
|
||||
testEscape('{{{', false);
|
||||
testEscape('{{~{', false);
|
||||
});
|
||||
it('should accept boolean for escape', function() {
|
||||
testEscape(true, true);
|
||||
testEscape({}, true);
|
||||
|
||||
testEscape(false, false);
|
||||
testEscape(undefined, false);
|
||||
});
|
||||
});
|
||||
describe('BlockNode', function() {
|
||||
it('should throw on mustache mismatch (old sexpr-less version)', function() {
|
||||
shouldThrow(function() {
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
|
||||
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
|
||||
}, Handlebars.Exception, "foo doesn't match bar");
|
||||
});
|
||||
describe('BlockStatement', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
||||
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
|
||||
});
|
||||
|
||||
it('stores location info', function(){
|
||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
||||
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
|
||||
{strip: {}}, {strip: {}},
|
||||
{
|
||||
strip: {},
|
||||
path: {original: 'foo'}
|
||||
},
|
||||
LOCATION_INFO);
|
||||
it('stores location info', function() {
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheStatement([{ original: 'foo'}], null, null, false, {});
|
||||
var block = new handlebarsEnv.AST.BlockStatement(
|
||||
mustacheNode,
|
||||
null, null,
|
||||
{body: []},
|
||||
{body: []},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
LOCATION_INFO);
|
||||
testLocationInfoStorage(block);
|
||||
});
|
||||
});
|
||||
describe('IdNode', function() {
|
||||
it('should throw on invalid path', function() {
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '..'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '.'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: 'this'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
|
||||
});
|
||||
|
||||
it('stores location info', function(){
|
||||
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
|
||||
describe('PathExpression', function() {
|
||||
it('stores location info', function() {
|
||||
var idNode = new handlebarsEnv.AST.PathExpression(false, 0, [], 'foo', LOCATION_INFO);
|
||||
testLocationInfoStorage(idNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe("HashNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
|
||||
describe('Hash', function() {
|
||||
it('stores location info', function() {
|
||||
var hash = new handlebarsEnv.AST.Hash([], LOCATION_INFO);
|
||||
testLocationInfoStorage(hash);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
|
||||
describe('ContentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var content = new handlebarsEnv.AST.ContentStatement('HI', LOCATION_INFO);
|
||||
testLocationInfoStorage(content);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CommentNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
|
||||
describe('CommentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var comment = new handlebarsEnv.AST.CommentStatement('HI', {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(comment);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NumberNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO);
|
||||
describe('NumberLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var integer = new handlebarsEnv.AST.NumberLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(integer);
|
||||
});
|
||||
});
|
||||
|
||||
describe("StringNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
|
||||
describe('StringLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var string = new handlebarsEnv.AST.StringLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(string);
|
||||
});
|
||||
});
|
||||
|
||||
describe("BooleanNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
|
||||
describe('BooleanLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var bool = new handlebarsEnv.AST.BooleanLiteral('true', LOCATION_INFO);
|
||||
testLocationInfoStorage(bool);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
|
||||
testLocationInfoStorage(data);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PartialNameNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pnn);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PartialNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO);
|
||||
describe('PartialStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var pn = new handlebarsEnv.AST.PartialStatement('so_partial', [], {}, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
describe("ProgramNode", function(){
|
||||
|
||||
describe("storing location info", function(){
|
||||
it("stores when `inverse` argument isn't passed", function(){
|
||||
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
|
||||
it("stores when `inverse` or `stripInverse` arguments passed", function(){
|
||||
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
|
||||
var clone = {
|
||||
strip: {},
|
||||
firstLine: 0,
|
||||
lastLine: 0,
|
||||
firstColumn: 0,
|
||||
lastColumn: 0
|
||||
};
|
||||
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
|
||||
// Assert that the newly created ProgramNode has the same location
|
||||
// information as the inverse
|
||||
testLocationInfoStorage(pn.inverse);
|
||||
});
|
||||
describe('Program', function() {
|
||||
it('storing location info', function() {
|
||||
var pn = new handlebarsEnv.AST.Program([], null, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Line Numbers", function(){
|
||||
var ast, statements;
|
||||
describe('Line Numbers', function() {
|
||||
var ast, body;
|
||||
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
|
||||
equals(node.firstLine, firstLine);
|
||||
equals(node.lastLine, lastLine);
|
||||
equals(node.firstColumn, firstColumn);
|
||||
equals(node.lastColumn, lastColumn);
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
|
||||
equals(node.loc.start.line, firstLine);
|
||||
equals(node.loc.start.column, firstColumn);
|
||||
equals(node.loc.end.line, lastLine);
|
||||
equals(node.loc.end.column, lastColumn);
|
||||
}
|
||||
|
||||
ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
|
||||
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
|
||||
statements = ast.statements;
|
||||
ast = Handlebars.parse('line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n' +
|
||||
'line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}');
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function(){
|
||||
var contentNode = statements[0];
|
||||
it('gets ContentNode line numbers', function() {
|
||||
var contentNode = body[0];
|
||||
testColumns(contentNode, 1, 1, 0, 7);
|
||||
});
|
||||
|
||||
it('gets MustacheNode line numbers', function(){
|
||||
var mustacheNode = statements[1];
|
||||
it('gets MustacheStatement line numbers', function() {
|
||||
var mustacheNode = body[1];
|
||||
testColumns(mustacheNode, 1, 1, 7, 21);
|
||||
});
|
||||
|
||||
it('gets line numbers correct when newlines appear', function(){
|
||||
var secondContentNode = statements[2];
|
||||
testColumns(secondContentNode, 1, 2, 21, 8);
|
||||
it('gets line numbers correct when newlines appear', function() {
|
||||
testColumns(body[2], 1, 2, 21, 8);
|
||||
});
|
||||
|
||||
it('gets MustacheNode line numbers correct across newlines', function(){
|
||||
var secondMustacheNode = statements[3];
|
||||
testColumns(secondMustacheNode, 2, 2, 8, 22);
|
||||
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||
var secondMustacheStatement = body[3];
|
||||
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||
});
|
||||
|
||||
it('gets the block helper information correct', function(){
|
||||
var blockHelperNode = statements[5];
|
||||
it('gets the block helper information correct', function() {
|
||||
var blockHelperNode = body[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function(){
|
||||
var blockHelperNode = statements[5],
|
||||
it('correctly records the line numbers the program of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 8, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 6, 13, 0);
|
||||
testColumns(inverse, 5, 7, 5, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone flags', function() {
|
||||
describe('mustache', function() {
|
||||
it('does not mark mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{comment}} ');
|
||||
equals(!!ast.body[0].value, true);
|
||||
equals(!!ast.body[2].value, true);
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, '');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n');
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(body[0].value, '');
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented partial as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{> partial }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[1].indent, ' ');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{> partial }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{> partial }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented comment as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{! comment }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{! comment }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{! comment }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+204
-124
@@ -1,217 +1,297 @@
|
||||
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
|
||||
global.handlebarsEnv = null;
|
||||
|
||||
beforeEach(function() {
|
||||
global.handlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
describe("basic context", function() {
|
||||
it("most basic", function() {
|
||||
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
|
||||
describe('basic context', function() {
|
||||
it('most basic', function() {
|
||||
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it("escaping", function() {
|
||||
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
|
||||
shouldCompileTo("content \\{{foo}}", { foo: "food" }, "content {{foo}}");
|
||||
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
|
||||
shouldCompileTo("content \\\\{{foo}}", { foo: "food" }, "content \\food");
|
||||
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
|
||||
it('escaping', function() {
|
||||
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
||||
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
||||
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
||||
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
|
||||
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
||||
});
|
||||
|
||||
it("compiling with a basic context", function() {
|
||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
||||
"It works if all the required keys are provided");
|
||||
it('compiling with a basic context', function() {
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'It works if all the required keys are provided');
|
||||
});
|
||||
|
||||
it("compiling with an undefined context", function() {
|
||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!");
|
||||
it('compiling with an undefined context', function() {
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
||||
|
||||
shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye");
|
||||
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
|
||||
});
|
||||
|
||||
it("comments", function() {
|
||||
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
||||
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
||||
"comments are ignored");
|
||||
it('comments', function() {
|
||||
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
||||
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'comments are ignored');
|
||||
|
||||
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
|
||||
});
|
||||
|
||||
it("boolean", function() {
|
||||
var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
||||
"booleans show the contents when true");
|
||||
it('boolean', function() {
|
||||
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'booleans show the contents when true');
|
||||
|
||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
||||
"booleans do not show the contents when false");
|
||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
||||
'booleans do not show the contents when false');
|
||||
});
|
||||
|
||||
it("zeros", function() {
|
||||
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
||||
"num1: 42, num2: 0");
|
||||
shouldCompileTo("num: {{.}}", 0, "num: 0");
|
||||
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
|
||||
it('zeros', function() {
|
||||
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
|
||||
'num1: 42, num2: 0');
|
||||
shouldCompileTo('num: {{.}}', 0, 'num: 0');
|
||||
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
|
||||
});
|
||||
it('false', function() {
|
||||
/*eslint-disable no-new-wrappers */
|
||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
||||
|
||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
||||
/*eslint-enable */
|
||||
});
|
||||
|
||||
it("newlines", function() {
|
||||
it('should handle undefined and null', function() {
|
||||
shouldCompileTo('{{awesome undefined null}}',
|
||||
{
|
||||
awesome: function(_undefined, _null, options) {
|
||||
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
|
||||
}
|
||||
},
|
||||
'true true object');
|
||||
shouldCompileTo('{{undefined}}',
|
||||
{
|
||||
'undefined': function() {
|
||||
return 'undefined!';
|
||||
}
|
||||
},
|
||||
'undefined!');
|
||||
shouldCompileTo('{{null}}',
|
||||
{
|
||||
'null': function() {
|
||||
return 'null!';
|
||||
}
|
||||
},
|
||||
'null!');
|
||||
});
|
||||
|
||||
it('newlines', function() {
|
||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
||||
});
|
||||
|
||||
it("escaping text", function() {
|
||||
it('escaping text', function() {
|
||||
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
|
||||
shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
|
||||
shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
|
||||
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
|
||||
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
|
||||
});
|
||||
|
||||
it("escaping expressions", function() {
|
||||
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
it('escaping expressions', function() {
|
||||
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with 3 handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with {{& handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
"by default expressions should be escaped");
|
||||
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
'by default expressions should be escaped');
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
"escaping should properly handle amperstands");
|
||||
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: <b>'}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
'escaping should properly handle amperstands');
|
||||
});
|
||||
|
||||
it("functions returning safestrings shouldn't be escaped", function() {
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
|
||||
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
|
||||
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
});
|
||||
|
||||
it("functions", function() {
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
||||
"functions are called and render their output");
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
||||
"functions are bound to the context");
|
||||
it('functions', function() {
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
|
||||
'functions are called and render their output');
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
|
||||
'functions are bound to the context');
|
||||
});
|
||||
|
||||
it("functions with context argument", function() {
|
||||
shouldCompileTo("{{awesome frank}}",
|
||||
it('functions with context argument', function() {
|
||||
shouldCompileTo('{{awesome frank}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
it("pathed functions with context argument", function() {
|
||||
shouldCompileTo("{{bar.awesome frank}}",
|
||||
it('pathed functions with context argument', function() {
|
||||
shouldCompileTo('{{bar.awesome frank}}',
|
||||
{bar: {awesome: function(context) { return context; }},
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
it("depthed functions with context argument", function() {
|
||||
shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}",
|
||||
it('depthed functions with context argument', function() {
|
||||
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
|
||||
it("block functions with context argument", function() {
|
||||
shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
|
||||
it('block functions with context argument', function() {
|
||||
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
|
||||
{awesome: function(context, options) { return options.fn(context); }},
|
||||
"inner 1", "block functions are called with context and options");
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it("depthed block functions with context argument", function() {
|
||||
shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
|
||||
it('depthed block functions with context argument', function() {
|
||||
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function(context, options) { return options.fn(context); }},
|
||||
"inner 1", "block functions are called with context and options");
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it("block functions without context argument", function() {
|
||||
shouldCompileTo("{{#awesome}}inner{{/awesome}}",
|
||||
it('block functions without context argument', function() {
|
||||
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
|
||||
{awesome: function(options) { return options.fn(this); }},
|
||||
"inner", "block functions are called with options");
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
it("pathed block functions without context argument", function() {
|
||||
shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}",
|
||||
{foo: {awesome: function(options) { return this; }}},
|
||||
"inner", "block functions are called with options");
|
||||
it('pathed block functions without context argument', function() {
|
||||
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
|
||||
{foo: {awesome: function() { return this; }}},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
it("depthed block functions without context argument", function() {
|
||||
shouldCompileTo("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}",
|
||||
{value: true, awesome: function(options) { return this; }},
|
||||
"inner", "block functions are called with options");
|
||||
it('depthed block functions without context argument', function() {
|
||||
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function() { return this; }},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
|
||||
|
||||
it("paths with hyphens", function() {
|
||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
it('paths with hyphens', function() {
|
||||
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
});
|
||||
|
||||
it("nested paths", function() {
|
||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Nested paths access nested objects");
|
||||
it('nested paths', function() {
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Nested paths access nested objects');
|
||||
});
|
||||
|
||||
it("nested paths with empty string value", function() {
|
||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
|
||||
"Goodbye world!", "Nested paths access nested objects with empty string");
|
||||
it('nested paths with empty string value', function() {
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
|
||||
'Goodbye world!', 'Nested paths access nested objects with empty string');
|
||||
});
|
||||
|
||||
it("literal paths", function() {
|
||||
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
it('literal paths', function() {
|
||||
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
});
|
||||
|
||||
it('literal references', function() {
|
||||
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
});
|
||||
|
||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
|
||||
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
|
||||
});
|
||||
|
||||
it("complex but empty paths", function() {
|
||||
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
|
||||
shouldCompileTo("{{person/name}}", {person: {}}, "");
|
||||
it('complex but empty paths', function() {
|
||||
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
|
||||
shouldCompileTo('{{person/name}}', {person: {}}, '');
|
||||
});
|
||||
|
||||
it("this keyword in paths", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
|
||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
||||
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
it('this keyword in paths', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
string = "{{#hellos}}{{this/text}}{{/hellos}}";
|
||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
||||
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
|
||||
string = '{{#hellos}}{{this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it("this keyword nested inside path", function() {
|
||||
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
||||
it('this keyword nested inside path', function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
|
||||
}, Error, 'Invalid path: text/this - 1:13');
|
||||
|
||||
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
|
||||
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
|
||||
});
|
||||
|
||||
it("this keyword in helpers", function() {
|
||||
it('this keyword in helpers', function() {
|
||||
var helpers = {foo: function(value) {
|
||||
return 'bar ' + value;
|
||||
}};
|
||||
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
|
||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
||||
shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
|
||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
||||
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
|
||||
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it("this keyword nested inside helpers param", function() {
|
||||
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
||||
it('this keyword nested inside helpers param', function() {
|
||||
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
}, Error, 'Invalid path: text/this - 1:17');
|
||||
|
||||
shouldCompileTo(
|
||||
'{{foo [this]}}',
|
||||
{foo: function(value) { return value; }, 'this': 'bar'},
|
||||
'bar');
|
||||
shouldCompileTo(
|
||||
'{{foo text/[this]}}',
|
||||
{foo: function(value) { return value; }, text: {'this': 'bar'}},
|
||||
'bar');
|
||||
});
|
||||
|
||||
it('pass string literals', function() {
|
||||
shouldCompileTo('{{"foo"}}', {}, '');
|
||||
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
|
||||
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
|
||||
});
|
||||
|
||||
it('pass number literals', function() {
|
||||
shouldCompileTo('{{12}}', {}, '');
|
||||
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34}}', {}, '');
|
||||
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
|
||||
});
|
||||
|
||||
it('pass boolean literals', function() {
|
||||
shouldCompileTo('{{true}}', {}, '');
|
||||
shouldCompileTo('{{true}}', { '': 'foo' }, '');
|
||||
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it('should handle literals in subexpression', function() {
|
||||
var helpers = {
|
||||
foo: function(arg) {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
|
||||
});
|
||||
});
|
||||
|
||||
+115
-49
@@ -1,86 +1,152 @@
|
||||
/*global CompilerContext, shouldCompileTo */
|
||||
describe('blocks', function() {
|
||||
it("array", function() {
|
||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"Arrays iterate over the contents when not empty");
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"Arrays ignore the contents when empty");
|
||||
it('array', function() {
|
||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'Arrays iterate over the contents when not empty');
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'Arrays ignore the contents when empty');
|
||||
});
|
||||
|
||||
it("array with @index", function() {
|
||||
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('array without data', function() {
|
||||
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||
});
|
||||
|
||||
it('array with @index', function() {
|
||||
var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
it("empty block", function() {
|
||||
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "cruel world!",
|
||||
"Arrays iterate over the contents when not empty");
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"Arrays ignore the contents when empty");
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it("block with complex lookup", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
|
||||
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
|
||||
it('empty block', function() {
|
||||
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'cruel world!',
|
||||
'Arrays iterate over the contents when not empty');
|
||||
|
||||
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
|
||||
"Templates can access variables in contexts up the stack with relative path syntax");
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'Arrays ignore the contents when empty');
|
||||
});
|
||||
|
||||
it("block with complex lookup using nested context", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
||||
it('block with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
|
||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
||||
|
||||
shouldCompileTo(string, hash, 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
|
||||
'Templates can access variables in contexts up the stack with relative path syntax');
|
||||
});
|
||||
|
||||
it('multiple blocks with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
||||
|
||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
||||
});
|
||||
|
||||
it('block with complex lookup using nested context', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("block with deep nested complex lookup", function() {
|
||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
|
||||
it('block with deep nested complex lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, hash, "Goodbye cruel OMG!");
|
||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
||||
});
|
||||
|
||||
describe('inverted sections', function() {
|
||||
it("inverted sections with unset value", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
it('inverted sections with unset value', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
|
||||
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
|
||||
});
|
||||
|
||||
it("inverted section with false value", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
it('inverted section with false value', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {goodbyes: false};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
|
||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
|
||||
});
|
||||
|
||||
it("inverted section with empty set", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
it('inverted section with empty set', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {goodbyes: []};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
|
||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
|
||||
});
|
||||
|
||||
it("block inverted sections", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
|
||||
"No people");
|
||||
it('block inverted sections', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
});
|
||||
it('chained inverted sections', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
});
|
||||
it('chained inverted sections with mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
|
||||
'No people');
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("block inverted sections with empty arrays", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
|
||||
"No people");
|
||||
it('block inverted sections with empty arrays', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
|
||||
'No people');
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone sections', function() {
|
||||
it('block standalone else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('block standalone chained else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('should handle nesting', function() {
|
||||
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compat mode', function() {
|
||||
it('block with deep recursive lookup lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
||||
});
|
||||
it('block with deep recursive pathed lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
||||
});
|
||||
it('block with missed recursive lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+227
-110
@@ -1,48 +1,51 @@
|
||||
/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */
|
||||
describe('builtin helpers', function() {
|
||||
describe('#if', function() {
|
||||
it("if", function() {
|
||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with boolean argument shows the contents when true");
|
||||
shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
|
||||
"if with string argument shows the contents");
|
||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
||||
"if with boolean argument does not show the contents when false");
|
||||
shouldCompileTo(string, {world: "world"}, "cruel world!",
|
||||
"if with undefined does not show the contents");
|
||||
shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
|
||||
"if with non-empty array shows the contents");
|
||||
shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
|
||||
"if with empty array does not show the contents");
|
||||
shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!",
|
||||
"if with zero does not show the contents");
|
||||
shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
{goodbye: 0, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with zero does not show the contents");
|
||||
it('if', function() {
|
||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with boolean argument shows the contents when true');
|
||||
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with string argument shows the contents');
|
||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
||||
'if with boolean argument does not show the contents when false');
|
||||
shouldCompileTo(string, {world: 'world'}, 'cruel world!',
|
||||
'if with undefined does not show the contents');
|
||||
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with non-empty array shows the contents');
|
||||
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
|
||||
'if with empty array does not show the contents');
|
||||
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
|
||||
'if with zero does not show the contents');
|
||||
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
|
||||
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with zero does not show the contents');
|
||||
});
|
||||
|
||||
it("if with function argument", function() {
|
||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with function shows the contents when function returns true");
|
||||
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with function shows the contents when function returns string");
|
||||
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
|
||||
"if with function does not show the contents when returns false");
|
||||
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
|
||||
"if with function does not show the contents when returns undefined");
|
||||
it('if with function argument', function() {
|
||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with function shows the contents when function returns true');
|
||||
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with function shows the contents when function returns string');
|
||||
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
|
||||
'if with function does not show the contents when returns false');
|
||||
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
|
||||
'if with function does not show the contents when returns undefined');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#with', function() {
|
||||
it("with", function() {
|
||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
||||
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
|
||||
it('with', function() {
|
||||
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
|
||||
});
|
||||
it("with with function argument", function() {
|
||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
||||
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
|
||||
it('with with function argument', function() {
|
||||
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
|
||||
});
|
||||
it('with with else', function() {
|
||||
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
|
||||
shouldCompileTo(string, {}, 'Person is not present');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,123 +56,162 @@ describe('builtin helpers', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("each", function() {
|
||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"each with array argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with array argument ignores the contents when empty");
|
||||
it('each', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'each with array argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'each with array argument ignores the contents when empty');
|
||||
});
|
||||
|
||||
it("each with an object and @key", function() {
|
||||
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
|
||||
it('each without data', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
|
||||
|
||||
hash = {goodbyes: 'cruel', world: 'world'};
|
||||
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
|
||||
});
|
||||
|
||||
it('each without context', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
shouldCompileTo(string, [,,,, ], 'cruel !');
|
||||
});
|
||||
|
||||
it('each with an object and @key', function() {
|
||||
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
|
||||
function Clazz() {
|
||||
this['<b>#1</b>'] = {text: 'goodbye'};
|
||||
this[2] = {text: 'GOODBYE'};
|
||||
}
|
||||
Clazz.prototype.foo = 'fail';
|
||||
var hash = {goodbyes: new Clazz(), world: 'world'};
|
||||
|
||||
// Object property iteration order is undefined according to ECMA spec,
|
||||
// so we need to check both possible orders
|
||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||
var actual = compileWithPartials(string, hash);
|
||||
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
||||
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
||||
var expected1 = '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||
var expected2 = '2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||
|
||||
equals(actual === expected1 || actual === expected2, true, "each with object argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with object argument ignores the contents when empty");
|
||||
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
|
||||
});
|
||||
|
||||
it("each with @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('each with @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it("each with nested @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('each with nested @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
|
||||
equal(result, '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it("each object with @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {'a': {text: "goodbye"}, b: {text: "Goodbye"}, c: {text: "GOODBYE"}}, world: "world"};
|
||||
it('each with block params', function() {
|
||||
var string = '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
|
||||
});
|
||||
|
||||
|
||||
it("each with @first", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('each object with @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it("each with nested @first", function() {
|
||||
var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
it('each with @first', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
|
||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it("each object with @first", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {'foo': {text: "goodbye"}, bar: {text: "Goodbye"}}, world: "world"};
|
||||
it('each with nested @first', function() {
|
||||
var string = '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
||||
equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it("each with @last", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('each object with @first', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "GOODBYE! cruel world!", "The @last variable is used");
|
||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it("each with nested @last", function() {
|
||||
var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
it('each with @last', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last variable is used");
|
||||
equal(result, 'GOODBYE! cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it("each with function argument", function() {
|
||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"each with array function argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with array function argument ignores the contents when empty");
|
||||
it('each object with @last', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'Goodbye! cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it("data passed to helpers", function() {
|
||||
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
||||
it('each with nested @last', function() {
|
||||
var string = '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it('each with function argument', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'each with array function argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'each with array function argument ignores the contents when empty');
|
||||
});
|
||||
|
||||
it('data passed to helpers', function() {
|
||||
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
@@ -181,33 +223,108 @@ describe('builtin helpers', function() {
|
||||
equal(result, 'a!b!c!', 'should output data');
|
||||
});
|
||||
|
||||
it("each on implicit context", function() {
|
||||
var string = "{{#each}}{{text}}! {{/each}}cruel world!";
|
||||
var hash = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];
|
||||
shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!");
|
||||
it('each on implicit context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
|
||||
template({});
|
||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
||||
});
|
||||
});
|
||||
|
||||
it("#log", function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
describe('#log', function() {
|
||||
/*eslint-disable no-console */
|
||||
if (typeof console === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg){
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
var $log,
|
||||
$info,
|
||||
$error;
|
||||
beforeEach(function() {
|
||||
$log = console.log;
|
||||
$info = console.info;
|
||||
$error = console.error;
|
||||
});
|
||||
afterEach(function() {
|
||||
console.log = $log;
|
||||
console.info = $info;
|
||||
console.error = $error;
|
||||
});
|
||||
|
||||
shouldCompileTo(string, hash, "", "log should not display");
|
||||
equals(1, levelArg, "should call log with 1");
|
||||
equals("whee", logArg, "should call log with 'whee'");
|
||||
it('should call logger at default level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg) {
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '', 'log should not display');
|
||||
equals(1, levelArg, 'should call log with 1');
|
||||
equals('whee', logArg, "should call log with 'whee'");
|
||||
});
|
||||
it('should call logger at data level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg) {
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(3, levelArg);
|
||||
equals('whee', logArg);
|
||||
});
|
||||
it('should output to info', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.info = function(info) {
|
||||
equals('whee', info);
|
||||
called = true;
|
||||
};
|
||||
console.log = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should log at data level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle missing logger', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
|
||||
console.error = undefined;
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
});
|
||||
/*eslint-enable no-console */
|
||||
});
|
||||
|
||||
|
||||
describe('#lookup', function() {
|
||||
it('should lookup arbitrary content', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
@@ -216,7 +333,7 @@ describe('builtin helpers', function() {
|
||||
});
|
||||
it('should not fail on undefined value', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
describe('compiler', function() {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('#equals', function() {
|
||||
function compile(string) {
|
||||
var ast = Handlebars.parse(string);
|
||||
return new Handlebars.Compiler().compile(ast, {});
|
||||
}
|
||||
|
||||
it('should treat as equal', function() {
|
||||
equal(compile('foo').equals(compile('foo')), true);
|
||||
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
||||
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
|
||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
|
||||
});
|
||||
it('should treat as not equal', function() {
|
||||
equal(compile('foo').equals(compile('bar')), false);
|
||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
||||
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
|
||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.compile(null);
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
|
||||
shouldThrow(function() {
|
||||
Handlebars.compile({});
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(Handlebars.compile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')], null, {}))(), 'Hello');
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#precompile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.precompile(null);
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
||||
shouldThrow(function() {
|
||||
Handlebars.precompile({});
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')]), null, {})), true);
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
equal(/return ""/.test(Handlebars.precompile('')), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
+94
-80
@@ -1,25 +1,24 @@
|
||||
/*global CompilerContext, Handlebars, handlebarsEnv, shouldThrow */
|
||||
describe('data', function() {
|
||||
it("passing in data to a compiled function that expects data - works with helpers", function() {
|
||||
var template = CompilerContext.compile("{{hello}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.adjective + " " + this.noun;
|
||||
return options.data.adjective + ' ' + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy cat", result, "Data output by helper");
|
||||
var result = template({noun: 'cat'}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('happy cat', result, 'Data output by helper');
|
||||
});
|
||||
|
||||
it("data can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{@hello}}");
|
||||
var result = template({}, { data: { hello: "hello" } });
|
||||
equals("hello", result, "@foo retrieves template data");
|
||||
it('data can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}');
|
||||
var result = template({}, { data: { hello: 'hello' } });
|
||||
equals('hello', result, '@foo retrieves template data');
|
||||
});
|
||||
|
||||
it("deep @foo triggers automatic top-level data", function() {
|
||||
it('deep @foo triggers automatic top-level data', function() {
|
||||
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
|
||||
|
||||
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||
@@ -28,196 +27,211 @@ describe('data', function() {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
|
||||
for (var prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
if (prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
}
|
||||
}
|
||||
return options.fn(this, { data: frame });
|
||||
};
|
||||
|
||||
var result = template({ foo: true }, { helpers: helpers });
|
||||
equals("Hello world", result, "Automatic data was triggered");
|
||||
equals('Hello world', result, 'Automatic data was triggered');
|
||||
});
|
||||
|
||||
it("parameter data can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{hello @world}}");
|
||||
it('parameter data can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{hello @world}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
||||
equals('Hello world', result, '@foo as a parameter retrieves template data');
|
||||
});
|
||||
|
||||
it("hash values can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{hello noun=@world}}");
|
||||
it('hash values can be looked up via @foo', function() {
|
||||
var template = CompilerContext.compile('{{hello noun=@world}}');
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return "Hello " + options.hash.noun;
|
||||
return 'Hello ' + options.hash.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
||||
equals('Hello world', result, '@foo as a parameter retrieves template data');
|
||||
});
|
||||
|
||||
it("nested parameter data can be looked up via @foo.bar", function() {
|
||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
||||
it('nested parameter data can be looked up via @foo.bar', function() {
|
||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
var result = template({}, { helpers: helpers, data: { world: {bar: 'world' } } });
|
||||
equals('Hello world', result, '@foo as a parameter retrieves template data');
|
||||
});
|
||||
|
||||
it("nested parameter data does not fail with @world.bar", function() {
|
||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
||||
it('nested parameter data does not fail with @world.bar', function() {
|
||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
return 'Hello ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
|
||||
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
|
||||
var result = template({}, { helpers: helpers, data: { foo: {bar: 'world' } } });
|
||||
equals('Hello undefined', result, '@foo as a parameter retrieves template data');
|
||||
});
|
||||
|
||||
it("parameter data throws when using complex scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
|
||||
it('parameter data throws when using complex scope references', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}';
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("data is inherited downstream", function() {
|
||||
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
|
||||
it('data can be functions', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}');
|
||||
var result = template({}, { data: { hello: function() { return 'hello'; } } });
|
||||
equals('hello', result);
|
||||
});
|
||||
it('data can be functions with params', function() {
|
||||
var template = CompilerContext.compile('{{@hello "hello"}}');
|
||||
var result = template({}, { data: { hello: function(arg) { return arg; } } });
|
||||
equals('hello', result);
|
||||
});
|
||||
|
||||
it('data is inherited downstream', function() {
|
||||
var template = CompilerContext.compile('{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}', { data: true });
|
||||
var helpers = {
|
||||
let: function(options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
for (var prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
if (prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
}
|
||||
}
|
||||
return options.fn(this, {data: frame});
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
|
||||
equals("2hello world1", result, "data variables are inherited downstream");
|
||||
var result = template({ bar: { baz: 'hello world' } }, { helpers: helpers, data: {} });
|
||||
equals('2hello world1', result, 'data variables are inherited downstream');
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
|
||||
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
||||
var template = CompilerContext.compile('{{>myPartial}}', {data: true});
|
||||
|
||||
var partials = {
|
||||
my_partial: CompilerContext.compile("{{hello}}", {data: true})
|
||||
myPartial: CompilerContext.compile('{{hello}}', {data: true})
|
||||
};
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.adjective + " " + this.noun;
|
||||
return options.data.adjective + ' ' + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}});
|
||||
equals("happy cat", result, "Data output by helper inside partial");
|
||||
var result = template({noun: 'cat'}, {helpers: helpers, partials: partials, data: {adjective: 'happy'}});
|
||||
equals('happy cat', result, 'Data output by helper inside partial');
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
|
||||
var template = CompilerContext.compile("{{hello world}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
|
||||
var template = CompilerContext.compile('{{hello world}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(noun, options) {
|
||||
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
|
||||
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world!", result, "Data output by helper");
|
||||
var result = template({exclaim: true, world: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('happy world!', result, 'Data output by helper');
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with block helpers", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn(this);
|
||||
},
|
||||
world: function(options) {
|
||||
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
|
||||
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world!", result, "Data output by helper");
|
||||
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('happy world!', result, 'Data output by helper');
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?"});
|
||||
return options.fn({exclaim: '?'});
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world?", result, "Data output by helper");
|
||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('happy world?', result, 'Data output by helper');
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.accessData + " " + options.fn({exclaim: "?"});
|
||||
return options.data.accessData + ' ' + options.fn({exclaim: '?'});
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
|
||||
equals("#win happy world?", result, "Data output by helper");
|
||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy', accessData: '#win'}});
|
||||
equals('#win happy world?', result, 'Data output by helper');
|
||||
});
|
||||
|
||||
it("you can override inherited data when invoking a helper", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
|
||||
it('you can override inherited data when invoking a helper', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world zomg}}{{/hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
|
||||
return options.fn({exclaim: '?', zomg: 'world'}, { data: {adjective: 'sad'} });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("sad world?", result, "Overriden data output by helper");
|
||||
var result = template({exclaim: true, zomg: 'planet'}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('sad world?', result, 'Overriden data output by helper');
|
||||
});
|
||||
|
||||
|
||||
it("you can override inherited data when invoking a helper with depth", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
it('you can override inherited data when invoking a helper with depth', function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{world ../zomg}}{{/hello}}', {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
|
||||
return options.fn({exclaim: '?'}, { data: {adjective: 'sad'} });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("sad world?", result, "Overriden data output by helper");
|
||||
var result = template({exclaim: true, zomg: 'world'}, {helpers: helpers, data: {adjective: 'happy'}});
|
||||
equals('sad world?', result, 'Overriden data output by helper');
|
||||
});
|
||||
|
||||
describe('@root', function() {
|
||||
|
||||
Vendored
+6
-4
@@ -1,14 +1,14 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs'),
|
||||
var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = undefined;
|
||||
global.Handlebars = 'no-conflict';
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
@@ -19,10 +19,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
Vendored
+14
-6
@@ -5,27 +5,35 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
|
||||
var result = compileWithPartials(string, hashOrArray, partials);
|
||||
if (result !== expected) {
|
||||
throw new Error("'" + expected + "' should === '" + result + "': " + message);
|
||||
throw new Error("'" + result + "' should === '" + expected + "': " + message);
|
||||
}
|
||||
};
|
||||
|
||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
||||
var template,
|
||||
ary,
|
||||
options;
|
||||
if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||
ary = [];
|
||||
ary.push(hashOrArray[0]);
|
||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||
options = typeof hashOrArray[3] === 'object' ? hashOrArray[3] : {compat: hashOrArray[3]};
|
||||
if (hashOrArray[4] != null) {
|
||||
options.data = !!hashOrArray[4];
|
||||
ary[1].data = hashOrArray[4];
|
||||
}
|
||||
} else {
|
||||
ary = [hashOrArray];
|
||||
}
|
||||
|
||||
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
|
||||
return template.apply(this, ary);
|
||||
};
|
||||
|
||||
|
||||
global.equals = global.equal = function(a, b, msg) {
|
||||
if (a !== b) {
|
||||
throw new Error("'" + b + "' should === '" + a + "'" + (msg ? ": " + msg : ''));
|
||||
throw new Error("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,10 +44,10 @@ global.shouldThrow = function(callback, type, msg) {
|
||||
failed = true;
|
||||
} catch (err) {
|
||||
if (type && !(err instanceof type)) {
|
||||
throw new Error('Type failure');
|
||||
throw new Error('Type failure: ' + err);
|
||||
}
|
||||
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
|
||||
throw new Error('Message failure');
|
||||
equal(msg, err.message);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
|
||||
Vendored
+2
-1
@@ -1,4 +1,3 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
global.Handlebars = require('../../lib');
|
||||
@@ -14,10 +13,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -1,3 +1,4 @@
|
||||
/*eslint-disable no-console */
|
||||
var fs = require('fs'),
|
||||
Mocha = require('mocha'),
|
||||
path = require('path');
|
||||
@@ -6,7 +7,7 @@ var errors = 0,
|
||||
testDir = path.dirname(__dirname),
|
||||
grep = process.argv[2];
|
||||
|
||||
var files = [ testDir + "/basic.js" ];
|
||||
var files = [ testDir + '/basic.js' ];
|
||||
|
||||
var files = fs.readdirSync(testDir)
|
||||
.filter(function(name) { return (/.*\.js$/).test(name); })
|
||||
@@ -15,7 +16,9 @@ var files = fs.readdirSync(testDir)
|
||||
run('./node', function() {
|
||||
run('./browser', function() {
|
||||
run('./runtime', function() {
|
||||
/*eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/*eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+11
-9
@@ -1,22 +1,22 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs'),
|
||||
var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = undefined;
|
||||
global.Handlebars = 'no-conflict';
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
|
||||
|
||||
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler')['default'];
|
||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function(template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.precompile = function(template, options) {
|
||||
return compiler.precompile(template, options, handlebarsEnv);
|
||||
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
|
||||
return compiler.precompile(precompileTemplate, precompileOptions, handlebarsEnv);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
@@ -27,8 +27,8 @@ global.CompilerContext = {
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.compile = function(template, options) {
|
||||
return compiler.compile(template, options, handlebarsEnv);
|
||||
handlebarsEnv.compile = function(compileTemplate, compileOptions) {
|
||||
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
@@ -39,10 +39,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[5,">= 2.0.0"],"main":function(depth0,helpers,partials,data) {
|
||||
return "";
|
||||
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
|
||||
+377
-264
@@ -1,322 +1,360 @@
|
||||
/*global CompilerContext, Handlebars, shouldCompileTo, shouldCompileToWithPartials, shouldThrow, handlebarsEnv */
|
||||
describe('helpers', function() {
|
||||
it("helper with complex lookup$", function() {
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
||||
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
it('helper with complex lookup$', function() {
|
||||
var string = '{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}';
|
||||
var hash = {prefix: '/root', goodbyes: [{text: 'Goodbye', url: 'goodbye'}]};
|
||||
var helpers = {link: function(prefix) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
|
||||
return '<a href="' + prefix + '/' + this.url + '">' + this.text + '</a>';
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
|
||||
shouldCompileTo(string, [hash, helpers], '<a href="/root/goodbye">Goodbye</a>');
|
||||
});
|
||||
|
||||
it("helper for raw block gets raw content", function() {
|
||||
var string = "{{{{raw}}}} {{test}} {{{{/raw}}}}";
|
||||
var hash = { test: "hello" };
|
||||
it('helper for raw block gets raw content', function() {
|
||||
var string = '{{{{raw}}}} {{test}} {{{{/raw}}}}';
|
||||
var hash = { test: 'hello' };
|
||||
var helpers = { raw: function(options) {
|
||||
return options.fn();
|
||||
} };
|
||||
shouldCompileTo(string, [hash, helpers], " {{test}} ",
|
||||
"raw block helper gets raw content");
|
||||
shouldCompileTo(string, [hash, helpers], ' {{test}} ',
|
||||
'raw block helper gets raw content');
|
||||
});
|
||||
|
||||
it("helper for raw block gets parameters", function() {
|
||||
var string = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}";
|
||||
var hash = { test: "hello" };
|
||||
|
||||
it('helper for raw block gets parameters', function() {
|
||||
var string = '{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}';
|
||||
var hash = { test: 'hello' };
|
||||
var helpers = { raw: function(a, b, c, options) {
|
||||
return options.fn() + a + b + c;
|
||||
} };
|
||||
shouldCompileTo(string, [hash, helpers], " {{test}} 123",
|
||||
"raw block helper gets raw content");
|
||||
shouldCompileTo(string, [hash, helpers], ' {{test}} 123',
|
||||
'raw block helper gets raw content');
|
||||
});
|
||||
|
||||
it("helper block with complex lookup expression", function() {
|
||||
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
|
||||
var hash = {name: "Alan"};
|
||||
|
||||
it('helper block with complex lookup expression', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan'};
|
||||
var helpers = {goodbyes: function(options) {
|
||||
var out = "";
|
||||
var byes = ["Goodbye", "goodbye", "GOODBYE"];
|
||||
for (var i = 0,j = byes.length; i < j; i++) {
|
||||
out += byes[i] + " " + options.fn(this) + "! ";
|
||||
var out = '';
|
||||
var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
|
||||
for (var i = 0, j = byes.length; i < j; i++) {
|
||||
out += byes[i] + ' ' + options.fn(this) + '! ';
|
||||
}
|
||||
return out;
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
|
||||
shouldCompileTo(string, [hash, helpers], 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ');
|
||||
});
|
||||
|
||||
it("helper with complex lookup and nested template", function() {
|
||||
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function (prefix, options) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
||||
it('helper with complex lookup and nested template', function() {
|
||||
var string = '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}';
|
||||
var hash = {prefix: '/root', goodbyes: [{text: 'Goodbye', url: 'goodbye'}]};
|
||||
var helpers = {link: function(prefix, options) {
|
||||
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
|
||||
}};
|
||||
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
|
||||
shouldCompileToWithPartials(string, [hash, helpers], false, '<a href="/root/goodbye">Goodbye</a>');
|
||||
});
|
||||
|
||||
it("helper with complex lookup and nested template in VM+Compiler", function() {
|
||||
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function (prefix, options) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
||||
it('helper with complex lookup and nested template in VM+Compiler', function() {
|
||||
var string = '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}';
|
||||
var hash = {prefix: '/root', goodbyes: [{text: 'Goodbye', url: 'goodbye'}]};
|
||||
var helpers = {link: function(prefix, options) {
|
||||
return '<a href="' + prefix + '/' + this.url + '">' + options.fn(this) + '</a>';
|
||||
}};
|
||||
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
|
||||
shouldCompileToWithPartials(string, [hash, helpers], true, '<a href="/root/goodbye">Goodbye</a>');
|
||||
});
|
||||
it('helper returning undefined value', function() {
|
||||
shouldCompileTo(' {{nothere}}', [{}, {nothere: function() {}}], ' ');
|
||||
shouldCompileTo(' {{#nothere}}{{/nothere}}', [{}, {nothere: function() {}}], ' ');
|
||||
});
|
||||
|
||||
it("block helper", function() {
|
||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
it('block helper', function() {
|
||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
|
||||
equal(result, "GOODBYE! cruel world!", "Block helper executed");
|
||||
var result = template({world: 'world'}, { helpers: {goodbyes: function(options) { return options.fn({text: 'GOODBYE'}); }}});
|
||||
equal(result, 'GOODBYE! cruel world!', 'Block helper executed');
|
||||
});
|
||||
|
||||
it("block helper staying in the same context", function() {
|
||||
var string = "{{#form}}<p>{{name}}</p>{{/form}}";
|
||||
it('block helper staying in the same context', function() {
|
||||
var string = '{{#form}}<p>{{name}}</p>{{/form}}';
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
|
||||
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
|
||||
var result = template({name: 'Yehuda'}, {helpers: {form: function(options) { return '<form>' + options.fn(this) + '</form>'; } }});
|
||||
equal(result, '<form><p>Yehuda</p></form>', 'Block helper executed with current context');
|
||||
});
|
||||
|
||||
it("block helper should have context in this", function() {
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
var link = function(options) {
|
||||
it('block helper should have context in this', function() {
|
||||
var source = '<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>';
|
||||
function link(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
};
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
}
|
||||
var data = { 'people': [
|
||||
{ 'name': 'Alan', 'id': 1 },
|
||||
{ 'name': 'Yehuda', 'id': 2 }
|
||||
]};
|
||||
|
||||
shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
|
||||
shouldCompileTo(source, [data, {link: link}], '<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>');
|
||||
});
|
||||
|
||||
it("block helper for undefined value", function() {
|
||||
shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
|
||||
it('block helper for undefined value', function() {
|
||||
shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, '');
|
||||
});
|
||||
|
||||
it("block helper passing a new context", function() {
|
||||
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
|
||||
it('block helper passing a new context', function() {
|
||||
var string = '{{#form yehuda}}<p>{{name}}</p>{{/form}}';
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
||||
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
|
||||
var result = template({yehuda: {name: 'Yehuda'}}, { helpers: {form: function(context, options) { return '<form>' + options.fn(context) + '</form>'; }}});
|
||||
equal(result, '<form><p>Yehuda</p></form>', 'Context variable resolved');
|
||||
});
|
||||
|
||||
it("block helper passing a complex path context", function() {
|
||||
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
|
||||
it('block helper passing a complex path context', function() {
|
||||
var string = '{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}';
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
||||
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
|
||||
var result = template({yehuda: {name: 'Yehuda', cat: {name: 'Harold'}}}, { helpers: {form: function(context, options) { return '<form>' + options.fn(context) + '</form>'; }}});
|
||||
equal(result, '<form><p>Harold</p></form>', 'Complex path variable resolved');
|
||||
});
|
||||
|
||||
it("nested block helpers", function() {
|
||||
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
|
||||
it('nested block helpers', function() {
|
||||
var string = '{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}';
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({
|
||||
yehuda: {name: "Yehuda" }
|
||||
yehuda: {name: 'Yehuda' }
|
||||
}, {
|
||||
helpers: {
|
||||
link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
|
||||
form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
|
||||
link: function(options) { return '<a href="' + this.name + '">' + options.fn(this) + '</a>'; },
|
||||
form: function(context, options) { return '<form>' + options.fn(context) + '</form>'; }
|
||||
}
|
||||
});
|
||||
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
|
||||
equal(result, '<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>', 'Both blocks executed');
|
||||
});
|
||||
|
||||
it("block helper inverted sections", function() {
|
||||
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
|
||||
var list = function(context, options) {
|
||||
it('block helper inverted sections', function() {
|
||||
var string = '{{#list people}}{{name}}{{^}}<em>Nobody\'s here</em>{{/list}}';
|
||||
function list(context, options) {
|
||||
if (context.length > 0) {
|
||||
var out = "<ul>";
|
||||
for(var i = 0,j=context.length; i < j; i++) {
|
||||
out += "<li>";
|
||||
var out = '<ul>';
|
||||
for (var i = 0, j = context.length; i < j; i++) {
|
||||
out += '<li>';
|
||||
out += options.fn(context[i]);
|
||||
out += "</li>";
|
||||
out += '</li>';
|
||||
}
|
||||
out += "</ul>";
|
||||
out += '</ul>';
|
||||
return out;
|
||||
} else {
|
||||
return "<p>" + options.inverse(this) + "</p>";
|
||||
return '<p>' + options.inverse(this) + '</p>';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]};
|
||||
var hash = {people: [{name: 'Alan'}, {name: 'Yehuda'}]};
|
||||
var empty = {people: []};
|
||||
var rootMessage = {
|
||||
people: [],
|
||||
message: "Nobody's here"
|
||||
message: 'Nobody\'s here'
|
||||
};
|
||||
|
||||
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
|
||||
var messageString = '{{#list people}}Hello{{^}}{{message}}{{/list}}';
|
||||
|
||||
// the meaning here may be kind of hard to catch, but list.not is always called,
|
||||
// so we should see the output of both
|
||||
shouldCompileTo(string, [hash, { list: list }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
|
||||
shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
|
||||
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody's here</p>", "the context of an inverse is the parent of the block");
|
||||
shouldCompileTo(string, [hash, { list: list }], '<ul><li>Alan</li><li>Yehuda</li></ul>', 'an inverse wrapper is passed in as a new context');
|
||||
shouldCompileTo(string, [empty, { list: list }], '<p><em>Nobody\'s here</em></p>', 'an inverse wrapper can be optionally called');
|
||||
shouldCompileTo(messageString, [rootMessage, { list: list }], '<p>Nobody's here</p>', 'the context of an inverse is the parent of the block');
|
||||
});
|
||||
|
||||
describe("helpers hash", function() {
|
||||
it("providing a helpers hash", function() {
|
||||
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
|
||||
"helpers hash is available");
|
||||
it('pathed lambas with parameters', function() {
|
||||
var hash = {
|
||||
helper: function() {
|
||||
return 'winning';
|
||||
}
|
||||
};
|
||||
hash.hash = hash;
|
||||
var helpers = {
|
||||
'./helper': function() {
|
||||
return 'fail';
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning');
|
||||
shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning');
|
||||
});
|
||||
|
||||
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
|
||||
"Goodbye cruel world!", "helpers hash is available inside other blocks");
|
||||
describe('helpers hash', function() {
|
||||
it('providing a helpers hash', function() {
|
||||
shouldCompileTo('Goodbye {{cruel}} {{world}}!', [{cruel: 'cruel'}, {world: function() { return 'world'; }}], 'Goodbye cruel world!',
|
||||
'helpers hash is available');
|
||||
|
||||
shouldCompileTo('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!', [{iter: [{cruel: 'cruel'}]}, {world: function() { return 'world'; }}],
|
||||
'Goodbye cruel world!', 'helpers hash is available inside other blocks');
|
||||
});
|
||||
|
||||
it("in cases of conflict, helpers win", function() {
|
||||
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence escaped expansion");
|
||||
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence simple expansion");
|
||||
it('in cases of conflict, helpers win', function() {
|
||||
shouldCompileTo('{{{lookup}}}', [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], 'helpers',
|
||||
'helpers hash has precedence escaped expansion');
|
||||
shouldCompileTo('{{lookup}}', [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], 'helpers',
|
||||
'helpers hash has precedence simple expansion');
|
||||
});
|
||||
|
||||
it("the helpers hash is available is nested contexts", function() {
|
||||
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
||||
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
|
||||
"helpers hash is available in nested contexts.");
|
||||
it('the helpers hash is available is nested contexts', function() {
|
||||
shouldCompileTo(
|
||||
'{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}',
|
||||
[
|
||||
{'outer': {'inner': {'unused': []}}},
|
||||
{'helper': function() { return 'helper'; }}
|
||||
],
|
||||
'helper',
|
||||
'helpers hash is available in nested contexts.');
|
||||
});
|
||||
|
||||
it("the helper hash should augment the global hash", function() {
|
||||
it('the helper hash should augment the global hash', function() {
|
||||
handlebarsEnv.registerHelper('test_helper', function() { return 'found it!'; });
|
||||
|
||||
shouldCompileTo(
|
||||
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [
|
||||
{cruel: "cruel"},
|
||||
{world: function() { return "world!"; }}
|
||||
'{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}', [
|
||||
{cruel: 'cruel'},
|
||||
{world: function() { return 'world!'; }}
|
||||
],
|
||||
"found it! Goodbye cruel world!!");
|
||||
'found it! Goodbye cruel world!!');
|
||||
});
|
||||
});
|
||||
|
||||
it("Multiple global helper registration", function() {
|
||||
var helpers = handlebarsEnv.helpers;
|
||||
handlebarsEnv.helpers = {};
|
||||
describe('registration', function() {
|
||||
it('unregisters', function() {
|
||||
handlebarsEnv.helpers = {};
|
||||
|
||||
handlebarsEnv.registerHelper({
|
||||
'if': helpers['if'],
|
||||
world: function() { return "world!"; },
|
||||
test_helper: function() { return 'found it!'; }
|
||||
handlebarsEnv.registerHelper('foo', function() {
|
||||
return 'fail';
|
||||
});
|
||||
handlebarsEnv.unregisterHelper('foo');
|
||||
equals(handlebarsEnv.helpers.foo, undefined);
|
||||
});
|
||||
|
||||
shouldCompileTo(
|
||||
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",
|
||||
[{cruel: "cruel"}],
|
||||
"found it! Goodbye cruel world!!");
|
||||
it('allows multiple globals', function() {
|
||||
var helpers = handlebarsEnv.helpers;
|
||||
handlebarsEnv.helpers = {};
|
||||
|
||||
handlebarsEnv.registerHelper({
|
||||
'if': helpers['if'],
|
||||
world: function() { return 'world!'; },
|
||||
testHelper: function() { return 'found it!'; }
|
||||
});
|
||||
|
||||
shouldCompileTo(
|
||||
'{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}',
|
||||
[{cruel: 'cruel'}],
|
||||
'found it! Goodbye cruel world!!');
|
||||
});
|
||||
it('fails with multiple and args', function() {
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.registerHelper({
|
||||
world: function() { return 'world!'; },
|
||||
testHelper: function() { return 'found it!'; }
|
||||
}, {});
|
||||
}, Error, 'Arg not supported with multiple helpers');
|
||||
});
|
||||
});
|
||||
|
||||
it("decimal number literals work", function() {
|
||||
var string = 'Message: {{hello -1.2 1.2}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times, times2) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
if(typeof times2 !== 'number') { times2 = "NaN"; }
|
||||
return "Hello " + times + " " + times2 + " times";
|
||||
it('decimal number literals work', function() {
|
||||
var string = 'Message: {{hello -1.2 1.2}}';
|
||||
var helpers = {hello: function(times, times2) {
|
||||
if (typeof times !== 'number') { times = 'NaN'; }
|
||||
if (typeof times2 !== 'number') { times2 = 'NaN'; }
|
||||
return 'Hello ' + times + ' ' + times2 + ' times';
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -1.2 1.2 times", "template with a negative integer literal");
|
||||
shouldCompileTo(string, [{}, helpers], 'Message: Hello -1.2 1.2 times', 'template with a negative integer literal');
|
||||
});
|
||||
|
||||
it("negative number literals work", function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
return "Hello " + times + " times";
|
||||
it('negative number literals work', function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var helpers = {hello: function(times) {
|
||||
if (typeof times !== 'number') { times = 'NaN'; }
|
||||
return 'Hello ' + times + ' times';
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
|
||||
shouldCompileTo(string, [{}, helpers], 'Message: Hello -12 times', 'template with a negative integer literal');
|
||||
});
|
||||
|
||||
describe("String literal parameters", function() {
|
||||
it("simple literals work", function() {
|
||||
var string = 'Message: {{hello "world" 12 true false}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param, times, bool1, bool2) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
|
||||
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
|
||||
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
|
||||
describe('String literal parameters', function() {
|
||||
it('simple literals work', function() {
|
||||
var string = 'Message: {{hello "world" 12 true false}}';
|
||||
var helpers = {hello: function(param, times, bool1, bool2) {
|
||||
if (typeof times !== 'number') { times = 'NaN'; }
|
||||
if (typeof bool1 !== 'boolean') { bool1 = 'NaB'; }
|
||||
if (typeof bool2 !== 'boolean') { bool2 = 'NaB'; }
|
||||
return 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2;
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
|
||||
shouldCompileTo(string, [{}, helpers], 'Message: Hello world 12 times: true false', 'template with a simple String literal');
|
||||
});
|
||||
|
||||
it("using a quote in the middle of a parameter raises an error", function() {
|
||||
var string = 'Message: {{hello wo"rld"}}';
|
||||
it('using a quote in the middle of a parameter raises an error', function() {
|
||||
var string = 'Message: {{hello wo"rld"}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("escaping a String is possible", function(){
|
||||
var string = 'Message: {{{hello "\\"world\\""}}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param) { return "Hello " + param; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
|
||||
it('escaping a String is possible', function() {
|
||||
var string = 'Message: {{{hello "\\"world\\""}}}';
|
||||
var helpers = {hello: function(param) { return 'Hello ' + param; }};
|
||||
shouldCompileTo(string, [{}, helpers], 'Message: Hello "world"', 'template with an escaped String literal');
|
||||
});
|
||||
|
||||
it("it works with ' marks", function() {
|
||||
var string = 'Message: {{{hello "Alan\'s world"}}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param) { return "Hello " + param; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
|
||||
var string = 'Message: {{{hello "Alan\'s world"}}}';
|
||||
var helpers = {hello: function(param) { return 'Hello ' + param; }};
|
||||
shouldCompileTo(string, [{}, helpers], "Message: Hello Alan's world", "template with a ' mark");
|
||||
});
|
||||
});
|
||||
|
||||
it("negative number literals work", function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
return "Hello " + times + " times";
|
||||
it('negative number literals work', function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var helpers = {hello: function(times) {
|
||||
if (typeof times !== 'number') { times = 'NaN'; }
|
||||
return 'Hello ' + times + ' times';
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
|
||||
shouldCompileTo(string, [{}, helpers], 'Message: Hello -12 times', 'template with a negative integer literal');
|
||||
});
|
||||
|
||||
describe("multiple parameters", function() {
|
||||
it("simple multi-params work", function() {
|
||||
var string = 'Message: {{goodbye cruel world}}';
|
||||
var hash = {cruel: "cruel", world: "world"};
|
||||
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
|
||||
describe('multiple parameters', function() {
|
||||
it('simple multi-params work', function() {
|
||||
var string = 'Message: {{goodbye cruel world}}';
|
||||
var hash = {cruel: 'cruel', world: 'world'};
|
||||
var helpers = {goodbye: function(cruel, world) { return 'Goodbye ' + cruel + ' ' + world; }};
|
||||
shouldCompileTo(string, [hash, helpers], 'Message: Goodbye cruel world', 'regular helpers with multiple params');
|
||||
});
|
||||
|
||||
it("block multi-params work", function() {
|
||||
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
|
||||
var hash = {cruel: "cruel", world: "world"};
|
||||
it('block multi-params work', function() {
|
||||
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
|
||||
var hash = {cruel: 'cruel', world: 'world'};
|
||||
var helpers = {goodbye: function(cruel, world, options) {
|
||||
return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
|
||||
return options.fn({greeting: 'Goodbye', adj: cruel, noun: world});
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
|
||||
shouldCompileTo(string, [hash, helpers], 'Message: Goodbye cruel world', 'block helpers with multiple params');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hash', function() {
|
||||
it("helpers can take an optional hash", function() {
|
||||
it('helpers can take an optional hash', function() {
|
||||
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES";
|
||||
return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world + ' ' + options.hash.times + ' TIMES';
|
||||
}
|
||||
};
|
||||
|
||||
var context = {};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash");
|
||||
equals(result, 'GOODBYE CRUEL WORLD 12 TIMES', 'Helper output hash');
|
||||
});
|
||||
|
||||
it("helpers can take an optional hash with booleans", function() {
|
||||
it('helpers can take an optional hash with booleans', function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
if (options.hash.print === true) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
|
||||
return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world;
|
||||
} else if (options.hash.print === false) {
|
||||
return "NOT PRINTING";
|
||||
return 'NOT PRINTING';
|
||||
} else {
|
||||
return "THIS SHOULD NOT HAPPEN";
|
||||
return 'THIS SHOULD NOT HAPPEN';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -325,146 +363,161 @@ describe('helpers', function() {
|
||||
|
||||
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}');
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
|
||||
equals(result, 'GOODBYE CRUEL WORLD', 'Helper output hash');
|
||||
|
||||
template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
|
||||
result = template(context, {helpers: helpers});
|
||||
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
|
||||
equals(result, 'NOT PRINTING', 'Boolean helper parameter honored');
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash", function() {
|
||||
it('block helpers can take an optional hash', function() {
|
||||
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
||||
return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this) + ' ' + options.hash.times + ' TIMES';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash with single quoted stings", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}");
|
||||
it('block helpers can take an optional hash with single quoted stings', function() {
|
||||
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
||||
return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this) + ' ' + options.hash.times + ' TIMES';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash with booleans", function() {
|
||||
it('block helpers can take an optional hash with booleans', function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
if (options.hash.print === true) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
|
||||
return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this);
|
||||
} else if (options.hash.print === false) {
|
||||
return "NOT PRINTING";
|
||||
return 'NOT PRINTING';
|
||||
} else {
|
||||
return "THIS SHOULD NOT HAPPEN";
|
||||
return 'THIS SHOULD NOT HAPPEN';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}');
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
|
||||
equals(result, 'GOODBYE CRUEL world', 'Boolean hash parameter honored');
|
||||
|
||||
template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
|
||||
result = template({}, {helpers: helpers});
|
||||
equals(result, "NOT PRINTING", "Boolean hash parameter honored");
|
||||
equals(result, 'NOT PRINTING', 'Boolean hash parameter honored');
|
||||
});
|
||||
});
|
||||
|
||||
describe("helperMissing", function() {
|
||||
it("if a context is not found, helperMissing is used", function() {
|
||||
describe('helperMissing', function() {
|
||||
it('if a context is not found, helperMissing is used', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
|
||||
var template = CompilerContext.compile('{{hello}} {{link_to world}}');
|
||||
template({});
|
||||
}, undefined, /Missing helper: 'link_to'/);
|
||||
}, undefined, /Missing helper: "link_to"/);
|
||||
});
|
||||
|
||||
it("if a context is not found, custom helperMissing is used", function() {
|
||||
var string = "{{hello}} {{link_to world}}";
|
||||
var context = { hello: "Hello", world: "world" };
|
||||
it('if a context is not found, custom helperMissing is used', function() {
|
||||
var string = '{{hello}} {{link_to world}}';
|
||||
var context = { hello: 'Hello', world: 'world' };
|
||||
|
||||
var helpers = {
|
||||
helperMissing: function(mesg, options) {
|
||||
if(options.name === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + mesg + "</a>");
|
||||
if (options.name === 'link_to') {
|
||||
return new Handlebars.SafeString('<a>' + mesg + '</a>');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
|
||||
shouldCompileTo(string, [context, helpers], 'Hello <a>world</a>');
|
||||
});
|
||||
|
||||
it('if a value is not found, custom helperMissing is used', function() {
|
||||
var string = '{{hello}} {{link_to}}';
|
||||
var context = { hello: 'Hello', world: 'world' };
|
||||
|
||||
var helpers = {
|
||||
helperMissing: function(options) {
|
||||
if (options.name === 'link_to') {
|
||||
return new Handlebars.SafeString('<a>winning</a>');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], 'Hello <a>winning</a>');
|
||||
});
|
||||
});
|
||||
|
||||
describe("knownHelpers", function() {
|
||||
it("Known helper should render helper", function() {
|
||||
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
|
||||
describe('knownHelpers', function() {
|
||||
it('Known helper should render helper', function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {knownHelpers: {hello: true}});
|
||||
|
||||
var result = template({}, {helpers: {hello: function() { return "foo"; }}});
|
||||
equal(result, "foo", "'foo' should === '" + result);
|
||||
var result = template({}, {helpers: {hello: function() { return 'foo'; }}});
|
||||
equal(result, 'foo', "'foo' should === '" + result);
|
||||
});
|
||||
|
||||
it("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
|
||||
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
|
||||
it('Unknown helper in knownHelpers only mode should be passed as undefined', function() {
|
||||
var template = CompilerContext.compile('{{typeof hello}}', {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
|
||||
|
||||
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
|
||||
equal(result, "undefined", "'undefined' should === '" + result);
|
||||
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return 'foo'; }}});
|
||||
equal(result, 'undefined', "'undefined' should === '" + result);
|
||||
});
|
||||
it("Builtin helpers available in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
|
||||
it('Builtin helpers available in knownHelpers only mode', function() {
|
||||
var template = CompilerContext.compile('{{#unless foo}}bar{{/unless}}', {knownHelpersOnly: true});
|
||||
|
||||
var result = template({});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
equal(result, 'bar', "'bar' should === '" + result);
|
||||
});
|
||||
it("Field lookup works in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||
it('Field lookup works in knownHelpers only mode', function() {
|
||||
var template = CompilerContext.compile('{{foo}}', {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: 'bar'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
equal(result, 'bar', "'bar' should === '" + result);
|
||||
});
|
||||
it("Conditional blocks work in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
||||
it('Conditional blocks work in knownHelpers only mode', function() {
|
||||
var template = CompilerContext.compile('{{#foo}}bar{{/foo}}', {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: 'baz'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
equal(result, 'bar', "'bar' should === '" + result);
|
||||
});
|
||||
it("Invert blocks work in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
||||
it('Invert blocks work in knownHelpers only mode', function() {
|
||||
var template = CompilerContext.compile('{{^foo}}bar{{/foo}}', {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: false});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
equal(result, 'bar', "'bar' should === '" + result);
|
||||
});
|
||||
it("Functions are bound to the context in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||
it('Functions are bound to the context in knownHelpers only mode', function() {
|
||||
var template = CompilerContext.compile('{{foo}}', {knownHelpersOnly: true});
|
||||
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
equal(result, 'bar', "'bar' should === '" + result);
|
||||
});
|
||||
it("Unknown helper call in knownHelpers only mode should throw", function() {
|
||||
it('Unknown helper call in knownHelpers only mode should throw', function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
|
||||
CompilerContext.compile('{{typeof hello}}', {knownHelpersOnly: true});
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe("blockHelperMissing", function() {
|
||||
it("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
|
||||
var string = "{{#truthy}}yep{{/truthy}}";
|
||||
describe('blockHelperMissing', function() {
|
||||
it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() {
|
||||
var string = '{{#truthy}}yep{{/truthy}}';
|
||||
var data = { truthy: function() { return true; } };
|
||||
shouldCompileTo(string, data, "yep");
|
||||
shouldCompileTo(string, data, 'yep');
|
||||
});
|
||||
it("lambdas resolved by blockHelperMissing are bound to the context", function() {
|
||||
var string = "{{#truthy}}yep{{/truthy}}";
|
||||
it('lambdas resolved by blockHelperMissing are bound to the context', function() {
|
||||
var string = '{{#truthy}}yep{{/truthy}}';
|
||||
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
|
||||
shouldCompileTo(string, boundData, "");
|
||||
shouldCompileTo(string, boundData, '');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -472,13 +525,13 @@ describe('helpers', function() {
|
||||
var context = {};
|
||||
var helpers = {
|
||||
blockHelperMissing: function() {
|
||||
return 'missing: ' + arguments[arguments.length-1].name;
|
||||
return 'missing: ' + arguments[arguments.length - 1].name;
|
||||
},
|
||||
helperMissing: function() {
|
||||
return 'helper missing: ' + arguments[arguments.length-1].name;
|
||||
return 'helper missing: ' + arguments[arguments.length - 1].name;
|
||||
},
|
||||
helper: function() {
|
||||
return 'ran: ' + arguments[arguments.length-1].name;
|
||||
return 'ran: ' + arguments[arguments.length - 1].name;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -498,7 +551,7 @@ describe('helpers', function() {
|
||||
shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in known helper calls', function() {
|
||||
var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true});
|
||||
var template = CompilerContext.compile('{{helper}}', {knownHelpers: {'helper': true}, knownHelpersOnly: true});
|
||||
|
||||
equal(template({}, {helpers: helpers}), 'ran: helper');
|
||||
});
|
||||
@@ -513,8 +566,8 @@ describe('helpers', function() {
|
||||
});
|
||||
|
||||
describe('name conflicts', function() {
|
||||
it("helpers take precedence over same-named context properties", function() {
|
||||
var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");
|
||||
it('helpers take precedence over same-named context properties', function() {
|
||||
var template = CompilerContext.compile('{{goodbye}} {{cruel world}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
@@ -522,21 +575,21 @@ describe('helpers', function() {
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
return 'cruel ' + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
goodbye: 'goodbye',
|
||||
world: 'world'
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
||||
equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
|
||||
});
|
||||
|
||||
it("helpers take precedence over same-named context properties$", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
|
||||
it('helpers take precedence over same-named context properties$', function() {
|
||||
var template = CompilerContext.compile('{{#goodbye}} {{cruel world}}{{/goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
@@ -544,21 +597,21 @@ describe('helpers', function() {
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
return 'cruel ' + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
goodbye: 'goodbye',
|
||||
world: 'world'
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
||||
equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
|
||||
});
|
||||
|
||||
it("Scoped names take precedence over helpers", function() {
|
||||
var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}");
|
||||
it('Scoped names take precedence over helpers', function() {
|
||||
var template = CompilerContext.compile('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
@@ -566,21 +619,21 @@ describe('helpers', function() {
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
return 'cruel ' + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
goodbye: 'goodbye',
|
||||
world: 'world'
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed");
|
||||
equals(result, 'goodbye cruel WORLD cruel GOODBYE', 'Helper not executed');
|
||||
});
|
||||
|
||||
it("Scoped names take precedence over block helpers", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
|
||||
it('Scoped names take precedence over block helpers', function() {
|
||||
var template = CompilerContext.compile('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
@@ -588,17 +641,77 @@ describe('helpers', function() {
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
return 'cruel ' + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
goodbye: 'goodbye',
|
||||
world: 'world'
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed");
|
||||
equals(result, 'GOODBYE cruel WORLD goodbye', 'Helper executed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('block params', function() {
|
||||
it('should take presedence over context values', function() {
|
||||
var hash = {value: 'foo'};
|
||||
var helpers = {
|
||||
goodbyes: function(options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
return options.fn({value: 'bar'}, {blockParams: [1, 2]});
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}', [hash, helpers], '1foo');
|
||||
});
|
||||
it('should take presedence over helper values', function() {
|
||||
var hash = {};
|
||||
var helpers = {
|
||||
value: function() {
|
||||
return 'foo';
|
||||
},
|
||||
goodbyes: function(options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
return options.fn({}, {blockParams: [1, 2]});
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}', [hash, helpers], '1foo');
|
||||
});
|
||||
it('should not take presedence over pathed values', function() {
|
||||
var hash = {value: 'bar'};
|
||||
var helpers = {
|
||||
value: function() {
|
||||
return 'foo';
|
||||
},
|
||||
goodbyes: function(options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
return options.fn(this, {blockParams: [1, 2]});
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}', [hash, helpers], 'barfoo');
|
||||
});
|
||||
it('should take presednece over parent block params', function() {
|
||||
var hash = {value: 'foo'},
|
||||
value = 1;
|
||||
var helpers = {
|
||||
goodbyes: function(options) {
|
||||
return options.fn({value: 'bar'}, {blockParams: options.fn.blockParams === 1 ? [value++, value++] : undefined});
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', [hash, helpers], '13foo');
|
||||
});
|
||||
|
||||
it('should allow block params on chained helpers', function() {
|
||||
var hash = {value: 'foo'};
|
||||
var helpers = {
|
||||
goodbyes: function(options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
return options.fn({value: 'bar'}, {blockParams: [1, 2]});
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}', [hash, helpers], '1foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/*global Handlebars, beforeEach, handlebarsEnv, shouldCompileTo */
|
||||
describe('javascript-compiler api', function() {
|
||||
if (!Handlebars.JavaScriptCompiler) {
|
||||
return;
|
||||
@@ -17,7 +16,15 @@ describe('javascript-compiler api', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
|
||||
return parent + '.bar_' + name;
|
||||
};
|
||||
shouldCompileTo("{{foo}}", { bar_foo: "food" }, "food");
|
||||
/*eslint-disable camelcase */
|
||||
shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
|
||||
/*eslint-enable camelcase */
|
||||
});
|
||||
|
||||
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
||||
// to avoid errors in older browsers.
|
||||
it('should handle reserved words', function() {
|
||||
shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food');
|
||||
});
|
||||
});
|
||||
describe('#compilerInfo', function() {
|
||||
@@ -39,7 +46,7 @@ describe('javascript-compiler api', function() {
|
||||
throw new Error('It didn\'t work');
|
||||
}
|
||||
};
|
||||
shouldCompileTo("{{foo}} ", { foo: "food" }, "food ");
|
||||
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
|
||||
});
|
||||
});
|
||||
describe('buffer', function() {
|
||||
@@ -59,13 +66,13 @@ describe('javascript-compiler api', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
|
||||
return this.quotedString('foo_');
|
||||
};
|
||||
shouldCompileTo("{{foo}} ", { foo: "food" }, "foo_food ");
|
||||
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
|
||||
});
|
||||
it('should allow append buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
|
||||
return $superAppend.call(this, string + ' + "_foo"');
|
||||
return $superAppend.call(this, [string, ' + "_foo"']);
|
||||
};
|
||||
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
|
||||
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Submodule
+1
Submodule spec/mustache added at 72233f3ffd
+102
-58
@@ -1,190 +1,234 @@
|
||||
/*global Handlebars */
|
||||
describe('parser', function() {
|
||||
if (!Handlebars.print) {
|
||||
return;
|
||||
}
|
||||
|
||||
function ast_for(template) {
|
||||
function astFor(template) {
|
||||
var ast = Handlebars.parse(template);
|
||||
return Handlebars.print(ast);
|
||||
}
|
||||
|
||||
it('parses simple mustaches', function() {
|
||||
equals(ast_for('{{foo}}'), "{{ ID:foo [] }}\n");
|
||||
equals(ast_for('{{foo?}}'), "{{ ID:foo? [] }}\n");
|
||||
equals(ast_for('{{foo_}}'), "{{ ID:foo_ [] }}\n");
|
||||
equals(ast_for('{{foo-}}'), "{{ ID:foo- [] }}\n");
|
||||
equals(ast_for('{{foo:}}'), "{{ ID:foo: [] }}\n");
|
||||
equals(astFor('{{123}}'), '{{ NUMBER{123} [] }}\n');
|
||||
equals(astFor('{{"foo"}}'), '{{ "foo" [] }}\n');
|
||||
equals(astFor('{{false}}'), '{{ BOOLEAN{false} [] }}\n');
|
||||
equals(astFor('{{true}}'), '{{ BOOLEAN{true} [] }}\n');
|
||||
equals(astFor('{{foo}}'), '{{ PATH:foo [] }}\n');
|
||||
equals(astFor('{{foo?}}'), '{{ PATH:foo? [] }}\n');
|
||||
equals(astFor('{{foo_}}'), '{{ PATH:foo_ [] }}\n');
|
||||
equals(astFor('{{foo-}}'), '{{ PATH:foo- [] }}\n');
|
||||
equals(astFor('{{foo:}}'), '{{ PATH:foo: [] }}\n');
|
||||
});
|
||||
|
||||
it('parses simple mustaches with data', function() {
|
||||
equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
|
||||
equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses simple mustaches with data paths', function() {
|
||||
equals(ast_for("{{@../foo}}"), "{{ @ID:foo [] }}\n");
|
||||
equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with paths', function() {
|
||||
equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
|
||||
equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with this/foo', function() {
|
||||
equals(ast_for("{{this/foo}}"), "{{ ID:foo [] }}\n");
|
||||
equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with - in a path', function() {
|
||||
equals(ast_for("{{foo-bar}}"), "{{ ID:foo-bar [] }}\n");
|
||||
equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with parameters', function() {
|
||||
equals(ast_for("{{foo bar}}"), "{{ ID:foo [ID:bar] }}\n");
|
||||
equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with string parameters', function() {
|
||||
equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
|
||||
equals(astFor('{{foo bar \"baz\" }}'), '{{ PATH:foo [PATH:bar, "baz"] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with NUMBER parameters', function() {
|
||||
equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{1}] }}\n");
|
||||
equals(astFor('{{foo 1}}'), '{{ PATH:foo [NUMBER{1}] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with BOOLEAN parameters', function() {
|
||||
equals(ast_for("{{foo true}}"), "{{ ID:foo [BOOLEAN{true}] }}\n");
|
||||
equals(ast_for("{{foo false}}"), "{{ ID:foo [BOOLEAN{false}] }}\n");
|
||||
equals(astFor('{{foo true}}'), '{{ PATH:foo [BOOLEAN{true}] }}\n');
|
||||
equals(astFor('{{foo false}}'), '{{ PATH:foo [BOOLEAN{false}] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with undefined and null paths', function() {
|
||||
equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n');
|
||||
equals(astFor('{{null}}'), '{{ NULL [] }}\n');
|
||||
});
|
||||
it('parses mustaches with undefined and null parameters', function() {
|
||||
equals(astFor('{{foo undefined null}}'), '{{ PATH:foo [UNDEFINED, NULL] }}\n');
|
||||
});
|
||||
|
||||
it('parses mutaches with DATA parameters', function() {
|
||||
equals(ast_for("{{foo @bar}}"), "{{ ID:foo [@ID:bar] }}\n");
|
||||
equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with hash arguments', function() {
|
||||
equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n");
|
||||
equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=NUMBER{1}} }}\n");
|
||||
equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
|
||||
equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
|
||||
equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID: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=true}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n');
|
||||
equals(astFor('{{foo bar=false}}'), '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n');
|
||||
equals(astFor('{{foo bar=@baz}}'), '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n');
|
||||
|
||||
equals(ast_for("{{foo bar=baz bat=bam}}"), "{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n");
|
||||
equals(ast_for("{{foo bar=baz bat=\"bam\"}}"), '{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
equals(astFor('{{foo bar=baz bat=bam}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n');
|
||||
equals(astFor('{{foo bar=baz bat="bam"}}'), '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n');
|
||||
|
||||
equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n');
|
||||
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
|
||||
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=NUMBER{1}} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
|
||||
equals(astFor('{{foo omg bar=baz bat=\"bam\"}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n');
|
||||
equals(astFor('{{foo omg bar=baz bat=\"bam\" baz=1}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n');
|
||||
equals(astFor('{{foo omg bar=baz bat=\"bam\" baz=true}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
|
||||
equals(astFor('{{foo omg bar=baz bat=\"bam\" baz=false}}'), '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
|
||||
});
|
||||
|
||||
it('parses contents followed by a mustache', function() {
|
||||
equals(ast_for("foo bar {{baz}}"), "CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
|
||||
equals(astFor('foo bar {{baz}}'), 'CONTENT[ \'foo bar \' ]\n{{ PATH:baz [] }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial', function() {
|
||||
equals(ast_for("{{> foo }}"), "{{> PARTIAL:foo }}\n");
|
||||
equals(astFor('{{> foo }}'), '{{> PARTIAL:foo }}\n');
|
||||
equals(astFor('{{> "foo" }}'), '{{> PARTIAL:foo }}\n');
|
||||
equals(astFor('{{> 1 }}'), '{{> PARTIAL:1 }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with context', function() {
|
||||
equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
|
||||
equals(astFor('{{> foo bar}}'), '{{> PARTIAL:foo PATH:bar }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with hash', function() {
|
||||
equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n");
|
||||
equals(astFor('{{> foo bar=bat}}'), '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with context and hash', function() {
|
||||
equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\n");
|
||||
equals(astFor('{{> foo bar bat=baz}}'), '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n');
|
||||
});
|
||||
|
||||
it('parses a partial with a complex name', function() {
|
||||
equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
|
||||
equals(astFor('{{> shared/partial?.bar}}'), '{{> PARTIAL:shared/partial?.bar }}\n');
|
||||
});
|
||||
|
||||
it('parses a comment', function() {
|
||||
equals(ast_for("{{! this is a comment }}"), "{{! ' this is a comment ' }}\n");
|
||||
equals(astFor('{{! this is a comment }}'), "{{! ' this is a comment ' }}\n");
|
||||
});
|
||||
|
||||
it('parses a multi-line comment', function() {
|
||||
equals(ast_for("{{!\nthis is a multi-line comment\n}}"), "{{! \'\nthis is a multi-line comment\n\' }}\n");
|
||||
equals(astFor('{{!\nthis is a multi-line comment\n}}'), "{{! \'\nthis is a multi-line comment\n\' }}\n");
|
||||
});
|
||||
|
||||
it('parses an inverse section', function() {
|
||||
equals(ast_for("{{#foo}} bar {{^}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
equals(astFor('{{#foo}} bar {{^}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
});
|
||||
|
||||
it('parses an inverse (else-style) section', function() {
|
||||
equals(ast_for("{{#foo}} bar {{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
equals(astFor('{{#foo}} bar {{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
});
|
||||
|
||||
it('parses multiple inverse sections', function() {
|
||||
equals(astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks', function() {
|
||||
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(astFor('{{#foo}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n');
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse section', function() {
|
||||
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(astFor('{{#foo}}{{^}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse (else-style) section', function() {
|
||||
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(astFor('{{#foo}}{{else}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse section', function() {
|
||||
equals(ast_for("{{#foo}} bar {{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
equals(astFor('{{#foo}} bar {{^}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse (else-style) section', function() {
|
||||
equals(ast_for("{{#foo}} bar {{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
equals(astFor('{{#foo}} bar {{else}}{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse section', function() {
|
||||
equals(ast_for("{{#foo}}{{^}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
equals(astFor('{{#foo}}{{^}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse (else-style) section', function() {
|
||||
equals(ast_for("{{#foo}}{{else}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
equals(astFor('{{#foo}}{{else}} bar {{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
});
|
||||
|
||||
it('parses a standalone inverse section', function() {
|
||||
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
equals(astFor('{{^foo}}bar{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
});
|
||||
it('throws on old inverse section', function() {
|
||||
shouldThrow(function() {
|
||||
astFor('{{else foo}}bar{{/foo}}');
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("raises if there's a Parse error", function() {
|
||||
it('parses block with block params', function() {
|
||||
equals(astFor('{{#foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
|
||||
});
|
||||
|
||||
it('parses inverse block with block params', function() {
|
||||
equals(astFor('{{^foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
|
||||
});
|
||||
it('parses chained inverse block with block params', function() {
|
||||
equals(astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'), "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n");
|
||||
});
|
||||
it('raises if there\'s a Parse error', function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("foo{{^}}bar");
|
||||
astFor('foo{{^}}bar');
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo}");
|
||||
astFor('{{foo}');
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo &}}");
|
||||
astFor('{{foo &}}');
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{#goodbyes}}{{/hellos}}");
|
||||
astFor('{{#goodbyes}}{{/hellos}}');
|
||||
}, Error, /goodbyes doesn't match hellos/);
|
||||
|
||||
shouldThrow(function() {
|
||||
ast_for("{{{{goodbyes}}}} {{{{/hellos}}}}");
|
||||
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
|
||||
}, Error, /goodbyes doesn't match hellos/);
|
||||
});
|
||||
|
||||
it('should handle invalid paths', function() {
|
||||
shouldThrow(function() {
|
||||
astFor('{{foo/../bar}}');
|
||||
}, Error, /Invalid path: foo\/\.\. - 1:2/);
|
||||
shouldThrow(function() {
|
||||
astFor('{{foo/./bar}}');
|
||||
}, Error, /Invalid path: foo\/\. - 1:2/);
|
||||
shouldThrow(function() {
|
||||
astFor('{{foo/this/bar}}');
|
||||
}, Error, /Invalid path: foo\/this - 1:2/);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors', function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\nmy\n{{foo}");
|
||||
astFor('hello\nmy\n{{foo}');
|
||||
}, Error, /Parse error on line 3/);
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\n\nmy\n\n{{foo}");
|
||||
astFor('hello\n\nmy\n\n{{foo}');
|
||||
}, Error, /Parse error on line 5/);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors when the first character is a newline', function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("\n\nhello\n\nmy\n\n{{foo}");
|
||||
astFor('\n\nhello\n\nmy\n\n{{foo}');
|
||||
}, Error, /Parse error on line 7/);
|
||||
});
|
||||
|
||||
describe('externally compiled AST', function() {
|
||||
it('can pass through an already-compiled AST', function() {
|
||||
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
|
||||
equals(astFor(new Handlebars.AST.Program([new Handlebars.AST.ContentStatement('Hello')], null)), 'CONTENT[ \'Hello\' ]\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+203
-96
@@ -1,140 +1,247 @@
|
||||
/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
|
||||
describe('partials', function() {
|
||||
it("basic partials", function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = "{{name}} ({{url}}) ";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Basic partials output based on current context.");
|
||||
it('basic partials', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) ';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
|
||||
});
|
||||
|
||||
it("partials with context", function() {
|
||||
var string = "Dudes: {{>dude dudes}}";
|
||||
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Partials can be passed a context");
|
||||
it('dynamic partials', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) ';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
var helpers = {
|
||||
partial: function() {
|
||||
return 'dude';
|
||||
}
|
||||
};
|
||||
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
|
||||
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial},, false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
|
||||
});
|
||||
it('failing dynamic partials', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) ';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
var helpers = {
|
||||
partial: function() {
|
||||
return 'missing';
|
||||
}
|
||||
};
|
||||
shouldThrow(function() {
|
||||
shouldCompileToWithPartials(string, [hash, helpers, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
|
||||
}, Handlebars.Exception, 'The partial missing could not be found');
|
||||
});
|
||||
|
||||
it("partials with undefined context", function() {
|
||||
var string = "Dudes: {{>dude dudes}}";
|
||||
var partial = "{{foo}} Empty";
|
||||
it('partials with context', function() {
|
||||
var string = 'Dudes: {{>dude dudes}}';
|
||||
var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
|
||||
'Partials can be passed a context');
|
||||
});
|
||||
|
||||
it('partials with undefined context', function() {
|
||||
var string = 'Dudes: {{>dude dudes}}';
|
||||
var partial = '{{foo}} Empty';
|
||||
var hash = {};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty");
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Empty');
|
||||
});
|
||||
|
||||
it("partials with parameters", function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}";
|
||||
var partial = "{{others.foo}}{{name}} ({{url}}) ";
|
||||
var hash = {foo: 'bar', dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: barYehuda (http://yehuda) barAlan (http://alan) ",
|
||||
"Basic partials output based on current context.");
|
||||
});
|
||||
|
||||
it("partial in a partial", function() {
|
||||
var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
|
||||
var dude = "{{name}} {{> url}} ";
|
||||
var url = "<a href='{{url}}'>{{url}}</a>";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
|
||||
});
|
||||
|
||||
it("rendering undefined partial throws an exception", function() {
|
||||
it('partials with duplicate parameters', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
|
||||
}, Error, 'Unsupported number of partial arguments: 2 - 1:7');
|
||||
});
|
||||
|
||||
it("rendering template partial in vm mode throws an exception", function() {
|
||||
it('partials with parameters', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
|
||||
var partial = '{{others.foo}}{{name}} ({{url}}) ';
|
||||
var hash = {foo: 'bar', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
|
||||
'Basic partials output based on current context.');
|
||||
});
|
||||
|
||||
it('partial in a partial', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
|
||||
var dude = '{{name}} {{> url}} ';
|
||||
var url = '<a href="{{url}}">{{url}}</a>';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, 'Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> ', 'Partials are rendered inside of other partials');
|
||||
});
|
||||
|
||||
it('rendering undefined partial throws an exception', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
var template = CompilerContext.compile('{{> whatever}}');
|
||||
template();
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it("rendering function partial in vm mode", function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = function(context) {
|
||||
it('registering undefined partial throws an exception', function() {
|
||||
shouldThrow(function() {
|
||||
var undef;
|
||||
handlebarsEnv.registerPartial('undefined_test', undef);
|
||||
}, Handlebars.Exception, 'Attempting to register a partial as undefined');
|
||||
});
|
||||
|
||||
it('rendering template partial in vm mode throws an exception', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{> whatever}}');
|
||||
template();
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it('rendering function partial in vm mode', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
function partial(context) {
|
||||
return context.name + ' (' + context.url + ') ';
|
||||
};
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Function partials output based in VM.");
|
||||
}
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileTo(string, [hash, {}, {dude: partial}], 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
|
||||
'Function partials output based in VM.');
|
||||
});
|
||||
|
||||
it("GH-14: a partial preceding a selector", function() {
|
||||
var string = "Dudes: {{>dude}} {{another_dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
|
||||
it('GH-14: a partial preceding a selector', function() {
|
||||
var string = 'Dudes: {{>dude}} {{anotherDude}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, 'Dudes: Jeepers Creepers', 'Regular selectors can follow a partial');
|
||||
});
|
||||
|
||||
it("Partials with slash paths", function() {
|
||||
var string = "Dudes: {{> shared/dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
it('Partials with slash paths', function() {
|
||||
var string = 'Dudes: {{> shared/dude}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
|
||||
});
|
||||
|
||||
it("Partials with slash and point paths", function() {
|
||||
var string = "Dudes: {{> shared/dude.thing}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths");
|
||||
it('Partials with slash and point paths', function() {
|
||||
var string = 'Dudes: {{> shared/dude.thing}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing': dude}], true, 'Dudes: Jeepers', 'Partials can use literal with points in paths');
|
||||
});
|
||||
|
||||
it("Global Partials", function() {
|
||||
handlebarsEnv.registerPartial('global_test', '{{another_dude}}');
|
||||
it('Global Partials', function() {
|
||||
handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}');
|
||||
|
||||
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
|
||||
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude': dude}], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
|
||||
|
||||
handlebarsEnv.unregisterPartial('globalTest');
|
||||
equals(handlebarsEnv.partials.globalTest, undefined);
|
||||
});
|
||||
|
||||
it("Multiple partial registration", function() {
|
||||
it('Multiple partial registration', function() {
|
||||
handlebarsEnv.registerPartial({
|
||||
'shared/dude': '{{name}}',
|
||||
global_test: '{{another_dude}}'
|
||||
globalTest: '{{anotherDude}}'
|
||||
});
|
||||
|
||||
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
|
||||
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash], true, 'Dudes: Jeepers Creepers', 'Partials can use globals or passed');
|
||||
});
|
||||
|
||||
it("Partials with integer path", function() {
|
||||
var string = "Dudes: {{> 404}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
it('Partials with integer path', function() {
|
||||
var string = 'Dudes: {{> 404}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {404: dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
|
||||
});
|
||||
|
||||
it("Partials with complex path", function() {
|
||||
var string = "Dudes: {{> 404/asdf?.bar}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
it('Partials with complex path', function() {
|
||||
var string = 'Dudes: {{> 404/asdf?.bar}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
|
||||
});
|
||||
|
||||
it("Partials with escaped", function() {
|
||||
var string = "Dudes: {{> [+404/asdf?.bar]}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
it('Partials with escaped', function() {
|
||||
var string = 'Dudes: {{> [+404/asdf?.bar]}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
|
||||
});
|
||||
|
||||
it("Partials with string", function() {
|
||||
var string = "Dudes: {{> \"+404/asdf?.bar\"}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
it('Partials with string', function() {
|
||||
var string = 'Dudes: {{> \'+404/asdf?.bar\'}}';
|
||||
var dude = '{{name}}';
|
||||
var hash = {name: 'Jeepers', anotherDude: 'Creepers'};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar': dude}], true, 'Dudes: Jeepers', 'Partials can use literal paths');
|
||||
});
|
||||
|
||||
it('should handle empty partial', function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = "";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: "); });
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
var partial = '';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: ');
|
||||
});
|
||||
|
||||
it('throw on missing partial', function() {
|
||||
var compile = handlebarsEnv.compile;
|
||||
handlebarsEnv.compile = undefined;
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], '');
|
||||
}, Error, /The partial dude could not be compiled/);
|
||||
handlebarsEnv.compile = compile;
|
||||
});
|
||||
|
||||
it('should pass compiler flags', function() {
|
||||
if (Handlebars.compile) {
|
||||
var env = Handlebars.create();
|
||||
env.registerPartial('partial', '{{foo}}');
|
||||
var template = env.compile('{{foo}} {{> partial}}', {noEscape: true});
|
||||
equal(template({foo: '<'}), '< <');
|
||||
}
|
||||
});
|
||||
|
||||
describe('standalone partials', function() {
|
||||
it('indented partials', function() {
|
||||
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
|
||||
var dude = '{{name}}\n';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true,
|
||||
'Dudes:\n Yehuda\n Alan\n');
|
||||
});
|
||||
it('nested indented partials', function() {
|
||||
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
|
||||
var dude = '{{name}}\n {{> url}}';
|
||||
var url = '{{url}}!\n';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true,
|
||||
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
|
||||
});
|
||||
it('prevent nested indented partials', function() {
|
||||
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
|
||||
var dude = '{{name}}\n {{> url}}';
|
||||
var url = '{{url}}!\n';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}, {preventIndent: true}], true,
|
||||
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compat mode', function() {
|
||||
it('partials can access parents', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) {{root}} ';
|
||||
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
|
||||
});
|
||||
it('partials can access parents without data', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) {{root}} ';
|
||||
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true, false], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
|
||||
});
|
||||
it('partials inherit compat', function() {
|
||||
var string = 'Dudes: {{> dude}}';
|
||||
var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
|
||||
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*eslint-disable no-console */
|
||||
describe('precompiler', function() {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var Handlebars = require('../lib'),
|
||||
Precompiler = require('../dist/cjs/precompiler'),
|
||||
fs = require('fs'),
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var log,
|
||||
logFunction,
|
||||
|
||||
precompile,
|
||||
minify,
|
||||
|
||||
file,
|
||||
content,
|
||||
writeFileSync;
|
||||
|
||||
beforeEach(function() {
|
||||
precompile = Handlebars.precompile;
|
||||
minify = uglify.minify;
|
||||
writeFileSync = fs.writeFileSync;
|
||||
|
||||
logFunction = console.log;
|
||||
log = '';
|
||||
console.log = function() {
|
||||
log += Array.prototype.join.call(arguments, '');
|
||||
};
|
||||
fs.writeFileSync = function(_file, _content) {
|
||||
file = _file;
|
||||
content = _content;
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
Handlebars.precompile = precompile;
|
||||
uglify.minify = minify;
|
||||
fs.writeFileSync = writeFileSync;
|
||||
console.log = logFunction;
|
||||
});
|
||||
|
||||
it('should output version', function() {
|
||||
Precompiler.cli({templates: [], version: true});
|
||||
equals(log, Handlebars.VERSION);
|
||||
});
|
||||
it('should throw if lacking templates', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: []});
|
||||
}, Handlebars.Exception, 'Must define at least one template or directory.');
|
||||
});
|
||||
it('should throw on missing template', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: ['foo']});
|
||||
}, Handlebars.Exception, 'Unable to open template file "foo"');
|
||||
});
|
||||
it('should throw when combining simple and minimized', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [__dirname], simple: true, min: true});
|
||||
}, Handlebars.Exception, 'Unable to minimize simple output');
|
||||
});
|
||||
it('should throw when combining simple and multiple templates', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
|
||||
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should throw when combining simple and directories', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [__dirname], simple: true});
|
||||
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should enumerate directories by extension', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hbs'});
|
||||
equal(/'example_2'/.test(log), true);
|
||||
log = '';
|
||||
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'handlebars'});
|
||||
equal(/'empty'/.test(log), true);
|
||||
equal(/'example_1'/.test(log), true);
|
||||
});
|
||||
it('should protect from regexp patterns', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hb(s'});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should output simple templates', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output amd templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'});
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, partial: true, extension: 'handlebars'});
|
||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, partial: true, extension: 'handlebars'});
|
||||
equal(/return Handlebars\.partials\[/.test(log), false);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function() {
|
||||
Handlebars.precompile = function() { return 'commonjs'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], commonjs: true, extension: 'handlebars'});
|
||||
equal(/template\(commonjs\)/.test(log), true);
|
||||
});
|
||||
|
||||
it('should set data flag', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', data: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should set known helpers', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should handle different root', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', root: 'foo/'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output to file system', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', output: 'file!'});
|
||||
equal(file, 'file!');
|
||||
equal(content, 'simple\n');
|
||||
equal(log, '');
|
||||
});
|
||||
it('should handle BOM', function() {
|
||||
Handlebars.precompile = function(template) { return template === 'a' ? 'simple' : 'fail'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/bom.handlebars'], simple: true, extension: 'handlebars', bom: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should output minimized templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
uglify.minify = function() { return {code: 'min'}; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
|
||||
equal(log, 'min');
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, map: 'foo.js.map', extension: 'handlebars'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
});
|
||||
});
|
||||
+92
-47
@@ -1,58 +1,84 @@
|
||||
/*global CompilerContext, Handlebars, shouldCompileTo, shouldThrow */
|
||||
describe('Regressions', function() {
|
||||
it("GH-94: Cannot read property of undefined", function() {
|
||||
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
|
||||
var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
|
||||
shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes",
|
||||
"Renders without an undefined property error");
|
||||
it('GH-94: Cannot read property of undefined', function() {
|
||||
var data = {
|
||||
'books': [{
|
||||
'title': 'The origin of species',
|
||||
'author': {
|
||||
'name': 'Charles Darwin'
|
||||
}
|
||||
}, {
|
||||
'title': 'Lazarillo de Tormes'
|
||||
}]
|
||||
};
|
||||
var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
|
||||
shouldCompileTo(string, data, 'The origin of speciesCharles DarwinLazarillo de Tormes',
|
||||
'Renders without an undefined property error');
|
||||
});
|
||||
|
||||
it("GH-150: Inverted sections print when they shouldn't", function() {
|
||||
var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}";
|
||||
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
|
||||
|
||||
shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context");
|
||||
shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined");
|
||||
shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false");
|
||||
shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true");
|
||||
shouldCompileTo(string, {}, 'not set :: ', "inverted sections run when property isn't present in context");
|
||||
shouldCompileTo(string, {set: undefined}, 'not set :: ', 'inverted sections run when property is undefined');
|
||||
shouldCompileTo(string, {set: false}, 'not set :: ', 'inverted sections run when property is false');
|
||||
shouldCompileTo(string, {set: true}, ' :: set', "inverted sections don't run when property is true");
|
||||
});
|
||||
|
||||
it("GH-158: Using array index twice, breaks the template", function() {
|
||||
var string = "{{arr.[0]}}, {{arr.[1]}}";
|
||||
var data = { "arr": [1,2] };
|
||||
it('GH-158: Using array index twice, breaks the template', function() {
|
||||
var string = '{{arr.[0]}}, {{arr.[1]}}';
|
||||
var data = { 'arr': [1, 2] };
|
||||
|
||||
shouldCompileTo(string, data, "1, 2", "it works as expected");
|
||||
shouldCompileTo(string, data, '1, 2', 'it works as expected');
|
||||
});
|
||||
|
||||
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
|
||||
var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
|
||||
var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
|
||||
+ '{{! Just ignore this business. }}\n'
|
||||
+ 'Check this out:\n'
|
||||
+ '{{#hasThings}}\n'
|
||||
+ '<ul>\n'
|
||||
+ '{{#things}}\n'
|
||||
+ '<li class={{className}}>{{word}}</li>\n'
|
||||
+ '{{/things}}</ul>.\n'
|
||||
+ '{{/hasThings}}\n'
|
||||
+ '{{^hasThings}}\n'
|
||||
+ '\n'
|
||||
+ '<small>Nothing to check out...</small>\n'
|
||||
+ '{{/hasThings}}';
|
||||
var data = {
|
||||
thing: function() {
|
||||
return "blah";
|
||||
return 'blah';
|
||||
},
|
||||
things: [
|
||||
{className: "one", word: "@fat"},
|
||||
{className: "two", word: "@dhg"},
|
||||
{className: "three", word:"@sayrer"}
|
||||
{className: 'one', word: '@fat'},
|
||||
{className: 'two', word: '@dhg'},
|
||||
{className: 'three', word: '@sayrer'}
|
||||
],
|
||||
hasThings: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
|
||||
var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
|
||||
+ 'Check this out:\n'
|
||||
+ '<ul>\n'
|
||||
+ '<li class=one>@fat</li>\n'
|
||||
+ '<li class=two>@dhg</li>\n'
|
||||
+ '<li class=three>@sayrer</li>\n'
|
||||
+ '</ul>.\n';
|
||||
shouldCompileTo(string, data, output);
|
||||
});
|
||||
|
||||
it('GH-408: Multiple loops fail', function() {
|
||||
var context = [
|
||||
{ name: "John Doe", location: { city: "Chicago" } },
|
||||
{ name: "Jane Doe", location: { city: "New York"} }
|
||||
{ name: 'John Doe', location: { city: 'Chicago' } },
|
||||
{ name: 'Jane Doe', location: { city: 'New York'} }
|
||||
];
|
||||
|
||||
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
|
||||
|
||||
var result = template(context);
|
||||
equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times');
|
||||
equals(result, 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', 'It should output multiple times');
|
||||
});
|
||||
|
||||
it('GS-428: Nested if else rendering', function() {
|
||||
@@ -77,11 +103,13 @@ describe('Regressions', function() {
|
||||
});
|
||||
|
||||
it('GH-534: Object prototype aliases', function() {
|
||||
/*eslint-disable no-extend-native */
|
||||
Object.prototype[0xD834] = true;
|
||||
|
||||
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
|
||||
|
||||
delete Object.prototype[0xD834];
|
||||
/*eslint-enable no-extend-native */
|
||||
});
|
||||
|
||||
it('GH-437: Matching escaping', function() {
|
||||
@@ -93,38 +121,55 @@ describe('Regressions', function() {
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("GH-676: Using array in escaping mustache fails", function() {
|
||||
var string = "{{arr}}";
|
||||
var data = { "arr": [1,2] };
|
||||
it('GH-676: Using array in escaping mustache fails', function() {
|
||||
var string = '{{arr}}';
|
||||
var data = { 'arr': [1, 2] };
|
||||
|
||||
shouldCompileTo(string, data, data.arr.toString(), "it works as expected");
|
||||
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
|
||||
});
|
||||
|
||||
it("Mustache man page", function() {
|
||||
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
|
||||
it('Mustache man page', function() {
|
||||
var string = 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
|
||||
var data = {
|
||||
"name": "Chris",
|
||||
"value": 10000,
|
||||
"taxed_value": 10000 - (10000 * 0.4),
|
||||
"in_ca": true
|
||||
'name': 'Chris',
|
||||
'value': 10000,
|
||||
'taxed_value': 10000 - (10000 * 0.4),
|
||||
'in_ca': true
|
||||
};
|
||||
|
||||
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
|
||||
shouldCompileTo(string, data, 'Hello Chris. You have just won $10000! Well, $6000, after taxes.', 'the hello world mustache example works');
|
||||
});
|
||||
|
||||
it("Passing falsy values to Handlebars.compile throws an error", function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(null);
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
||||
it('GH-731: zero context rendering', function() {
|
||||
shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is ~ ');
|
||||
});
|
||||
|
||||
if (Handlebars.AST) {
|
||||
it("can pass through an already-compiled AST via compile/precompile", function() {
|
||||
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
|
||||
});
|
||||
it('GH-820: zero pathed rendering', function() {
|
||||
shouldCompileTo('{{foo.bar}}', {foo: 0}, '');
|
||||
});
|
||||
|
||||
it("can pass through an empty string", function() {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
});
|
||||
}
|
||||
it('GH-837: undefined values for helpers', function() {
|
||||
var helpers = {
|
||||
str: function(value) { return value + ''; }
|
||||
};
|
||||
|
||||
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
|
||||
});
|
||||
|
||||
it('GH-926: Depths and de-dupe', function() {
|
||||
var context = {
|
||||
name: 'foo',
|
||||
data: [
|
||||
1
|
||||
],
|
||||
notData: [
|
||||
1
|
||||
]
|
||||
};
|
||||
|
||||
var template = CompilerContext.compile('{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}');
|
||||
|
||||
var result = template(context);
|
||||
equals(result, 'foo');
|
||||
});
|
||||
});
|
||||
|
||||
+7
-7
@@ -1,21 +1,21 @@
|
||||
if (typeof(require) !== 'undefined' && require.extensions[".handlebars"]) {
|
||||
if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
|
||||
describe('Require', function() {
|
||||
it('Load .handlebars files with require()', function() {
|
||||
var template = require("./artifacts/example_1");
|
||||
equal(template, require("./artifacts/example_1.handlebars"));
|
||||
var template = require('./artifacts/example_1');
|
||||
equal(template, require('./artifacts/example_1.handlebars'));
|
||||
|
||||
var expected = 'foo\n';
|
||||
var result = template({foo: "foo"});
|
||||
var result = template({foo: 'foo'});
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function() {
|
||||
var template = require("./artifacts/example_2");
|
||||
equal(template, require("./artifacts/example_2.hbs"));
|
||||
var template = require('./artifacts/example_2');
|
||||
equal(template, require('./artifacts/example_2.hbs'));
|
||||
|
||||
var expected = 'Hello, World!\n';
|
||||
var result = template({name: "World"});
|
||||
var result = template({name: 'World'});
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
describe('runtime', function() {
|
||||
describe('#template', function() {
|
||||
it('should throw on invalid templates', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({});
|
||||
}, Error, 'Unknown template object: object');
|
||||
shouldThrow(function() {
|
||||
Handlebars.template();
|
||||
}, Error, 'Unknown template object: undefined');
|
||||
shouldThrow(function() {
|
||||
Handlebars.template('');
|
||||
}, Error, 'Unknown template object: string');
|
||||
});
|
||||
it('should throw on version mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true,
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1]
|
||||
});
|
||||
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true,
|
||||
compiler: [Handlebars.COMPILER_REVISION - 1]
|
||||
});
|
||||
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true
|
||||
});
|
||||
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#child', function() {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should throw for depthed methods without depths', function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
template._setup({});
|
||||
template._setup({});
|
||||
template._child(1);
|
||||
}, Error, 'must pass parent depths');
|
||||
});
|
||||
|
||||
it('should throw for block param methods without params', function() {
|
||||
shouldThrow(function() {
|
||||
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
template._setup({});
|
||||
template._setup({});
|
||||
template._child(1);
|
||||
}, Error, 'must pass block params');
|
||||
});
|
||||
it('should expose child template', function() {
|
||||
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
equal(template._child(1)(), 'bar');
|
||||
equal(template._child(1)(), 'bar');
|
||||
});
|
||||
it('should render depthed content', function() {
|
||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||
// Calling twice to hit the non-compiled case.
|
||||
equal(template._child(1, undefined, [], [{bar: 'baz'}])(), 'baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#noConflict', function() {
|
||||
if (!CompilerContext.browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should reset on no conflict', function() {
|
||||
var reset = Handlebars;
|
||||
Handlebars.noConflict();
|
||||
equal(Handlebars, 'no-conflict');
|
||||
|
||||
Handlebars = 'really, none';
|
||||
reset.noConflict();
|
||||
equal(Handlebars, 'really, none');
|
||||
|
||||
Handlebars = reset;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
try {
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
var SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
}
|
||||
} catch (err) {
|
||||
/* NOP for in browser */
|
||||
}
|
||||
|
||||
describe('source-map', function() {
|
||||
if (!Handlebars.precompile || !SourceMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should safely include source map info', function() {
|
||||
var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
|
||||
|
||||
equal(!!template.code, true);
|
||||
equal(!!template.map, !CompilerContext.browser);
|
||||
});
|
||||
it('should map source properly', function() {
|
||||
var templateSource = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
|
||||
template = Handlebars.precompile(templateSource, {destName: 'dest.js', srcName: 'src.hbs'});
|
||||
|
||||
if (template.map) {
|
||||
var consumer = new SourceMapConsumer(template.map),
|
||||
lines = template.code.split('\n'),
|
||||
srcLines = templateSource.split('\n'),
|
||||
|
||||
generated = grepLine('" b"', lines),
|
||||
source = grepLine(' b', srcLines);
|
||||
|
||||
var mapped = consumer.originalPositionFor(generated);
|
||||
equal(mapped.line, source.line);
|
||||
equal(mapped.column, source.column);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function grepLine(token, lines) {
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var column = lines[i].indexOf(token);
|
||||
if (column >= 0) {
|
||||
return {
|
||||
line: i + 1,
|
||||
column: column
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
describe('spec', function() {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs');
|
||||
|
||||
var specDir = __dirname + '/mustache/specs/';
|
||||
var specs = _.filter(fs.readdirSync(specDir), function(name) {
|
||||
return (/.*\.json$/).test(name);
|
||||
});
|
||||
|
||||
_.each(specs, function(name) {
|
||||
var spec = require(specDir + name);
|
||||
_.each(spec.tests, function(test) {
|
||||
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec
|
||||
// We also do not support alternative delimeters
|
||||
if (name === '~lambdas.json'
|
||||
|
||||
// We also choose to throw if paritals are not found
|
||||
|| (name === 'partials.json' && test.name === 'Failed Lookup')
|
||||
|
||||
// We nest the entire response from partials, not just the literals
|
||||
|| (name === 'partials.json' && test.name === 'Standalone Indentation')
|
||||
|
||||
|| (/\{\{\=/).test(test.template)
|
||||
|| _.any(test.partials, function(partial) { return (/\{\{\=/).test(partial); })) {
|
||||
it.skip(name + ' - ' + test.name);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = _.clone(test.data);
|
||||
if (data.lambda) {
|
||||
// Blergh
|
||||
/*eslint-disable no-eval */
|
||||
data.lambda = eval('(' + data.lambda.js + ')');
|
||||
/*eslint-enable no-eval */
|
||||
}
|
||||
it(name + ' - ' + test.name, function() {
|
||||
if (test.partials) {
|
||||
shouldCompileToWithPartials(test.template, [data, {}, test.partials, true], true, test.expected, test.desc + ' "' + test.template + '"');
|
||||
} else {
|
||||
shouldCompileTo(test.template, [data, {}, {}, true], test.expected, test.desc + ' "' + test.template + '"');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
var Exception = Handlebars.Exception;
|
||||
|
||||
describe('strict', function() {
|
||||
describe('strict mode', function() {
|
||||
it('should error on missing property lookup', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {strict: true});
|
||||
|
||||
template({});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
});
|
||||
it('should error on missing child', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
|
||||
equals(template({hello: {bar: 'foo'}}), 'foo');
|
||||
|
||||
shouldThrow(function() {
|
||||
template({hello: {}});
|
||||
}, Exception, /"bar" not defined in/);
|
||||
});
|
||||
it('should handle explicit undefined', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
|
||||
|
||||
equals(template({hello: {bar: undefined}}), '');
|
||||
});
|
||||
it('should error on missing property lookup in known helpers mode', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true});
|
||||
|
||||
template({});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
});
|
||||
it('should error on missing context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {strict: true});
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function() {
|
||||
var template = CompilerContext.compile('{{@hello}}', {strict: true});
|
||||
equals(template(undefined, {data: {hello: 'foo'}}), 'foo');
|
||||
|
||||
shouldThrow(function() {
|
||||
template();
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('should not run helperMissing for helper calls', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello foo}}', {strict: true});
|
||||
|
||||
template({foo: true});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true});
|
||||
|
||||
template({foo: true});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
});
|
||||
it('should throw on ambiguous blocks', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true});
|
||||
|
||||
template({});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true});
|
||||
|
||||
template({});
|
||||
}, Exception, /"hello" not defined in/);
|
||||
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true});
|
||||
|
||||
template({hello: {}});
|
||||
}, Exception, /"bar" not defined in/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assume objects', function() {
|
||||
it('should ignore missing property', function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
|
||||
|
||||
equal(template({}), '');
|
||||
});
|
||||
it('should ignore missing child', function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
|
||||
|
||||
equal(template({hello: {}}), '');
|
||||
});
|
||||
it('should error on missing object', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});
|
||||
|
||||
template({});
|
||||
}, Error);
|
||||
});
|
||||
it('should error on missing context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('should error on missing data lookup', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true});
|
||||
|
||||
template();
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('should execute blockHelperMissing', function() {
|
||||
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {assumeObjects: true});
|
||||
|
||||
equals(template({}), 'foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
+56
-56
@@ -1,43 +1,43 @@
|
||||
describe('string params mode', function() {
|
||||
it("arguments to helpers can be retrieved from options hash in string form", function() {
|
||||
it('arguments to helpers can be retrieved from options hash in string form', function() {
|
||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun) {
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
|
||||
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
|
||||
equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output");
|
||||
equals(result, 'HELP ME MY BOSS is.a slave.driver', 'String parameters output');
|
||||
});
|
||||
|
||||
it("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
|
||||
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
|
||||
var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun, options) {
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' +
|
||||
return 'HELP ME MY BOSS ' + passiveVoice + ' ' +
|
||||
noun + ': ' + options.fn(this);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
|
||||
equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output");
|
||||
equals(result, 'HELP ME MY BOSS is.a slave.driver: help :(', 'String parameters output');
|
||||
});
|
||||
|
||||
it("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
|
||||
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return "STOP ME FROM READING HACKER NEWS I " +
|
||||
options.contexts[0][desire] + " " + noun;
|
||||
return 'STOP ME FROM READING HACKER NEWS I ' +
|
||||
options.contexts[0][desire] + ' ' + noun;
|
||||
},
|
||||
|
||||
"with": function(context, options) {
|
||||
'with': function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
@@ -48,88 +48,88 @@ describe('string params mode', function() {
|
||||
need: 'need-a'
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
|
||||
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', 'Proper context variable output');
|
||||
});
|
||||
|
||||
it("information about the types is passed along", function() {
|
||||
var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true });
|
||||
it('information about the types is passed along', function() {
|
||||
var template = CompilerContext.compile("{{tomdale 'need' dad.joke true false}}", { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
||||
equal(options.types[0], 'STRING', "the string type is passed");
|
||||
equal(options.types[1], 'ID', "the expression type is passed");
|
||||
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
|
||||
equal(desire, "need", "the string form is passed for strings");
|
||||
equal(noun, "dad.joke", "the string form is passed for expressions");
|
||||
equal(trueBool, true, "raw booleans are passed through");
|
||||
equal(falseBool, false, "raw booleans are passed through");
|
||||
return "Helper called";
|
||||
equal(options.types[0], 'StringLiteral', 'the string type is passed');
|
||||
equal(options.types[1], 'PathExpression', 'the expression type is passed');
|
||||
equal(options.types[2], 'BooleanLiteral', 'the expression type is passed');
|
||||
equal(desire, 'need', 'the string form is passed for strings');
|
||||
equal(noun, 'dad.joke', 'the string form is passed for expressions');
|
||||
equal(trueBool, true, 'raw booleans are passed through');
|
||||
equal(falseBool, false, 'raw booleans are passed through');
|
||||
return 'Helper called';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it("hash parameters get type information", function() {
|
||||
var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true });
|
||||
it('hash parameters get type information', function() {
|
||||
var template = CompilerContext.compile("{{tomdale he.says desire='need' noun=dad.joke bool=true}}", { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, "he.says");
|
||||
equal(options.types[0], "ID");
|
||||
equal(exclamation, 'he.says');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
|
||||
equal(options.hashTypes.desire, "STRING");
|
||||
equal(options.hashTypes.noun, "ID");
|
||||
equal(options.hashTypes.bool, "BOOLEAN");
|
||||
equal(options.hash.desire, "need");
|
||||
equal(options.hash.noun, "dad.joke");
|
||||
equal(options.hashTypes.desire, 'StringLiteral');
|
||||
equal(options.hashTypes.noun, 'PathExpression');
|
||||
equal(options.hashTypes.bool, 'BooleanLiteral');
|
||||
equal(options.hash.desire, 'need');
|
||||
equal(options.hash.noun, 'dad.joke');
|
||||
equal(options.hash.bool, true);
|
||||
return "Helper called";
|
||||
return 'Helper called';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it("hash parameters get context information", function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true });
|
||||
it('hash parameters get context information', function() {
|
||||
var template = CompilerContext.compile("{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}", { stringParams: true });
|
||||
|
||||
var context = {dale: {}};
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, "he.says");
|
||||
equal(options.types[0], "ID");
|
||||
equal(exclamation, 'he.says');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
|
||||
equal(options.contexts.length, 1);
|
||||
equal(options.hashContexts.noun, context);
|
||||
equal(options.hash.desire, "need");
|
||||
equal(options.hash.noun, "dad.joke");
|
||||
equal(options.hash.desire, 'need');
|
||||
equal(options.hash.noun, 'dad.joke');
|
||||
equal(options.hash.bool, true);
|
||||
return "Helper called";
|
||||
return 'Helper called';
|
||||
},
|
||||
"with": function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
'with': function(withContext, options) {
|
||||
return options.fn(options.contexts[0][withContext]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(context, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
equal(result, 'Helper called');
|
||||
});
|
||||
|
||||
it("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
|
||||
it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return "STOP ME FROM READING HACKER NEWS I " +
|
||||
options.contexts[0][desire] + " " + noun + " " +
|
||||
return 'STOP ME FROM READING HACKER NEWS I ' +
|
||||
options.contexts[0][desire] + ' ' + noun + ' ' +
|
||||
options.fn(this);
|
||||
},
|
||||
|
||||
"with": function(context, options) {
|
||||
'with': function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
@@ -140,23 +140,23 @@ describe('string params mode', function() {
|
||||
need: 'need-a'
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
|
||||
equals(result, 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', 'Proper context variable output');
|
||||
});
|
||||
|
||||
it("with nested block ambiguous", function() {
|
||||
it('with nested block ambiguous', function() {
|
||||
var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
'with': function(options) {
|
||||
return "WITH";
|
||||
var helpers = {
|
||||
'with': function() {
|
||||
return 'WITH';
|
||||
},
|
||||
view: function() {
|
||||
return "VIEW";
|
||||
return 'VIEW';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "WITH");
|
||||
equals(result, 'WITH');
|
||||
});
|
||||
|
||||
it('should handle DATA', function() {
|
||||
@@ -164,8 +164,8 @@ describe('string params mode', function() {
|
||||
|
||||
var helpers = {
|
||||
foo: function(bar, options) {
|
||||
equal(bar, 'bar');
|
||||
equal(options.types[0], 'DATA');
|
||||
equal(bar, '@bar');
|
||||
equal(options.types[0], 'PathExpression');
|
||||
return 'Foo!';
|
||||
}
|
||||
};
|
||||
|
||||
+112
-72
@@ -1,141 +1,182 @@
|
||||
/*global CompilerContext, shouldCompileTo */
|
||||
describe('subexpressions', function() {
|
||||
it("arg-less helper", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {};
|
||||
var helpers = {
|
||||
it('arg-less helper', function() {
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {};
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
return val + val;
|
||||
},
|
||||
bar: function() {
|
||||
return "LOL";
|
||||
return 'LOL';
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
});
|
||||
|
||||
it("helper w args", function() {
|
||||
var string = '{{blog (equal a b)}}';
|
||||
it('helper w args', function() {
|
||||
var string = '{{blog (equal a b)}}';
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
});
|
||||
|
||||
it("mixed paths and helpers", function() {
|
||||
var string = '{{blog baz.bat (equal a b) baz.bar}}';
|
||||
it('mixed paths and helpers', function() {
|
||||
var string = '{{blog baz.bat (equal a b) baz.bar}}';
|
||||
|
||||
var context = { bar: "LOL", baz: {bat: 'foo!', bar: 'bar!'} };
|
||||
var helpers = {
|
||||
var context = { bar: 'LOL', baz: {bat: 'foo!', bar: 'bar!'} };
|
||||
var helpers = {
|
||||
blog: function(val, that, theOther) {
|
||||
return "val is " + val + ', ' + that + ' and ' + theOther;
|
||||
return 'val is ' + val + ', ' + that + ' and ' + theOther;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is foo!, true and bar!");
|
||||
shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!');
|
||||
});
|
||||
|
||||
it("supports much nesting", function() {
|
||||
var string = '{{blog (equal (equal true true) true)}}';
|
||||
it('supports much nesting', function() {
|
||||
var string = '{{blog (equal (equal true true) true)}}';
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
});
|
||||
|
||||
it("provides each nested helper invocation its own options hash", function() {
|
||||
it('GH-800 : Complex subexpressions', function() {
|
||||
var context = {a: 'a', b: 'b', c: {c: 'c'}, d: 'd', e: {e: 'e'}};
|
||||
var helpers = {
|
||||
dash: function(a, b) {
|
||||
return a + '-' + b;
|
||||
},
|
||||
concat: function(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo("{{dash 'abc' (concat a b)}}", [context, helpers], 'abc-ab');
|
||||
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
|
||||
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
|
||||
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
|
||||
shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
|
||||
});
|
||||
|
||||
it('provides each nested helper invocation its own options hash', function() {
|
||||
var string = '{{equal (equal true true) true}}';
|
||||
|
||||
var lastOptions = null;
|
||||
var helpers = {
|
||||
var helpers = {
|
||||
equal: function(x, y, options) {
|
||||
if (!options || options === lastOptions) {
|
||||
throw new Error("options hash was reused");
|
||||
throw new Error('options hash was reused');
|
||||
}
|
||||
lastOptions = options;
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], "true");
|
||||
shouldCompileTo(string, [{}, helpers], 'true');
|
||||
});
|
||||
|
||||
it("with hashes", function() {
|
||||
var string = '{{blog (equal (equal true true) true fun="yes")}}';
|
||||
it('with hashes', function() {
|
||||
var string = "{{blog (equal (equal true true) true fun='yes')}}";
|
||||
|
||||
var context = { bar: "LOL" };
|
||||
var helpers = {
|
||||
var context = { bar: 'LOL' };
|
||||
var helpers = {
|
||||
blog: function(val) {
|
||||
return "val is " + val;
|
||||
return 'val is ' + val;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "val is true");
|
||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
||||
});
|
||||
|
||||
it("as hashes", function() {
|
||||
var string = '{{blog fun=(equal (blog fun=1) "val is 1")}}';
|
||||
it('as hashes', function() {
|
||||
var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}";
|
||||
|
||||
var helpers = {
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
return "val is " + options.hash.fun;
|
||||
return 'val is ' + options.hash.fun;
|
||||
},
|
||||
equal: function(x, y) {
|
||||
return x === y;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], "val is true");
|
||||
shouldCompileTo(string, [{}, helpers], 'val is true');
|
||||
});
|
||||
|
||||
it("multiple subexpressions in a hash", function() {
|
||||
it('multiple subexpressions in a hash', function() {
|
||||
var string = '{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
|
||||
|
||||
var helpers = {
|
||||
input: function(options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
|
||||
},
|
||||
t: function(defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
}
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], '<input aria-label="Name" placeholder="Example User" />');
|
||||
});
|
||||
|
||||
it("in string params mode,", function() {
|
||||
it('multiple subexpressions in a hash with context', function() {
|
||||
var string = '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
|
||||
|
||||
var context = {
|
||||
item: {
|
||||
field: 'Name',
|
||||
placeholder: 'Example User'
|
||||
}
|
||||
};
|
||||
|
||||
var helpers = {
|
||||
input: function(options) {
|
||||
var hash = options.hash;
|
||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||
return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
|
||||
},
|
||||
t: function(defaultString) {
|
||||
return new Handlebars.SafeString(defaultString);
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], '<input aria-label="Name" placeholder="Example User" />');
|
||||
});
|
||||
|
||||
it('in string params mode,', function() {
|
||||
var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
snog: function(a, b, options) {
|
||||
equals(a, 'foo');
|
||||
equals(options.types.length, 2, "string params for outer helper processed correctly");
|
||||
equals(options.types[0], 'sexpr', "string params for outer helper processed correctly");
|
||||
equals(options.types[1], 'ID', "string params for outer helper processed correctly");
|
||||
equals(options.types.length, 2, 'string params for outer helper processed correctly');
|
||||
equals(options.types[0], 'SubExpression', 'string params for outer helper processed correctly');
|
||||
equals(options.types[1], 'PathExpression', 'string params for outer helper processed correctly');
|
||||
return a + b;
|
||||
},
|
||||
|
||||
blorg: function(a, options) {
|
||||
equals(options.types.length, 1, "string params for inner helper processed correctly");
|
||||
equals(options.types[0], 'ID', "string params for inner helper processed correctly");
|
||||
equals(options.types.length, 1, 'string params for inner helper processed correctly');
|
||||
equals(options.types[0], 'PathExpression', 'string params for inner helper processed correctly');
|
||||
return a;
|
||||
}
|
||||
};
|
||||
@@ -145,54 +186,53 @@ describe('subexpressions', function() {
|
||||
yeah: {}
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "fooyeah");
|
||||
equals(result, 'fooyeah');
|
||||
});
|
||||
|
||||
it("as hashes in string params mode", function() {
|
||||
|
||||
it('as hashes in string params mode', function() {
|
||||
var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
var helpers = {
|
||||
blog: function(options) {
|
||||
equals(options.hashTypes.fun, 'sexpr');
|
||||
return "val is " + options.hash.fun;
|
||||
equals(options.hashTypes.fun, 'SubExpression');
|
||||
return 'val is ' + options.hash.fun;
|
||||
},
|
||||
bork: function() {
|
||||
return "BORK";
|
||||
return 'BORK';
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "val is BORK");
|
||||
equals(result, 'val is BORK');
|
||||
});
|
||||
|
||||
it("subexpression functions on the context", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {
|
||||
it('subexpression functions on the context', function() {
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {
|
||||
bar: function() {
|
||||
return "LOL";
|
||||
return 'LOL';
|
||||
}
|
||||
};
|
||||
var helpers = {
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
return val + val;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
});
|
||||
|
||||
it("subexpressions can't just be property lookups", function() {
|
||||
var string = "{{foo (bar)}}!";
|
||||
var context = {
|
||||
bar: "LOL"
|
||||
var string = '{{foo (bar)}}!';
|
||||
var context = {
|
||||
bar: 'LOL'
|
||||
};
|
||||
var helpers = {
|
||||
var helpers = {
|
||||
foo: function(val) {
|
||||
return val+val;
|
||||
return val + val;
|
||||
}
|
||||
};
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo(string, [context, helpers], "LOLLOL!");
|
||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+150
-126
@@ -21,7 +21,7 @@ describe('Tokenizer', function() {
|
||||
var out = [],
|
||||
token;
|
||||
|
||||
while (token = lexer.lex()) {
|
||||
while ((token = lexer.lex())) {
|
||||
var result = parser.terminals_[token] || token;
|
||||
if (!result || result === 'EOF' || result === 'INVALID') {
|
||||
break;
|
||||
@@ -33,370 +33,394 @@ describe('Tokenizer', function() {
|
||||
}
|
||||
|
||||
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() {
|
||||
var result = tokenize("{{foo}}");
|
||||
var result = tokenize('{{foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('supports unescaping with &', function() {
|
||||
var result = tokenize("{{&bar}}");
|
||||
var result = tokenize('{{&bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[0], "OPEN", "{{&");
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
shouldBeToken(result[0], 'OPEN', '{{&');
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports unescaping with {{{', function() {
|
||||
var result = tokenize("{{{bar}}}");
|
||||
var result = tokenize('{{{bar}}}');
|
||||
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} {{baz}}");
|
||||
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
});
|
||||
|
||||
it('supports escaping multiple delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} \\{{baz}}");
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[5], "CONTENT", "{{baz}}");
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaping a triple stash', function() {
|
||||
var result = tokenize("{{foo}} \\{{{bar}}} {{baz}}");
|
||||
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[4], "CONTENT", "{{{bar}}} ");
|
||||
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
|
||||
});
|
||||
|
||||
it('supports escaping escape character', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} {{baz}}");
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping multiple escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\\\{{baz}}");
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[7], "CONTENT", " \\");
|
||||
shouldBeToken(result[9], "ID", "baz");
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[9], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped mustaches after escaped escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\{{baz}}");
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[4], "OPEN", "{{");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[7], "CONTENT", " ");
|
||||
shouldBeToken(result[8], "CONTENT", "{{baz}}");
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[4], 'OPEN', '{{');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' ');
|
||||
shouldBeToken(result[8], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaped escape characters after escaped mustaches', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} \\\\{{baz}}");
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[5], "CONTENT", "\\");
|
||||
shouldBeToken(result[6], "OPEN", "{{");
|
||||
shouldBeToken(result[7], "ID", "baz");
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '\\');
|
||||
shouldBeToken(result[6], 'OPEN', '{{');
|
||||
shouldBeToken(result[7], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped escape character on a triple stash', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{{bar}}} {{baz}}");
|
||||
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('tokenizes a simple path', function() {
|
||||
var result = tokenize("{{foo/bar}}");
|
||||
var result = tokenize('{{foo/bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows dot notation', function() {
|
||||
var result = tokenize("{{foo.bar}}");
|
||||
var result = tokenize('{{foo.bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
|
||||
shouldMatchTokens(tokenize("{{foo.bar.baz}}"), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows path literals with []', function() {
|
||||
var result = tokenize("{{foo.[bar]}}");
|
||||
var result = tokenize('{{foo.[bar]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows multiple path literals on a line with []', function() {
|
||||
var result = tokenize("{{foo.[bar]}}{{foo.[baz]}}");
|
||||
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
|
||||
var result = tokenize("{{.}}");
|
||||
var result = tokenize('{{.}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
|
||||
var result = tokenize("{{../foo/bar}}");
|
||||
var result = tokenize('{{../foo/bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "..");
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with .. as a parent path', function() {
|
||||
var result = tokenize("{{../foo.bar}}");
|
||||
var result = tokenize('{{../foo.bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "..");
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function() {
|
||||
var result = tokenize("{{this/foo}}");
|
||||
var result = tokenize('{{this/foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "this");
|
||||
shouldBeToken(result[3], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'this');
|
||||
shouldBeToken(result[3], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo }}");
|
||||
var result = tokenize('{{ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo \n bar }}");
|
||||
var result = tokenize('{{ foo \n bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes raw content as "CONTENT"', function() {
|
||||
var result = tokenize("foo {{ bar }} baz");
|
||||
var result = tokenize('foo {{ bar }} baz');
|
||||
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
|
||||
shouldBeToken(result[0], "CONTENT", "foo ");
|
||||
shouldBeToken(result[4], "CONTENT", " baz");
|
||||
shouldBeToken(result[0], 'CONTENT', 'foo ');
|
||||
shouldBeToken(result[4], 'CONTENT', ' baz');
|
||||
});
|
||||
|
||||
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{> foo}}");
|
||||
var result = tokenize('{{> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{> foo bar }}");
|
||||
var result = tokenize('{{> foo bar }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo}}");
|
||||
var result = tokenize('{{>foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo }}");
|
||||
var result = tokenize('{{>foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo/bar.baz }}");
|
||||
var result = tokenize('{{>foo/bar.baz }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a comment as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{! this is a comment }} bar {{ baz }}");
|
||||
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a comment ");
|
||||
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}");
|
||||
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a {{comment}} ");
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}");
|
||||
var result = tokenize('foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}');
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a\n{{comment}}\n");
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
|
||||
});
|
||||
|
||||
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
|
||||
var result = tokenize("{{#foo}}content{{/foo}}");
|
||||
var result = tokenize('{{#foo}}content{{/foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
|
||||
shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
it('tokenizes inverse sections as "INVERSE"', function() {
|
||||
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{ else }}'), ['INVERSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
|
||||
var result = tokenize("{{^foo}}");
|
||||
var result = tokenize('{{^foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function() {
|
||||
var result = tokenize("{{^ foo }}");
|
||||
var result = tokenize('{{^ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar baz }}");
|
||||
var result = tokenize('{{ foo bar baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[2], "ID", "bar");
|
||||
shouldBeToken(result[3], "ID", "baz");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[2], 'ID', 'bar');
|
||||
shouldBeToken(result[3], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar \"baz\" }}");
|
||||
var result = tokenize('{{ foo bar \'baz\' }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz");
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar \'baz\' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz");
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params with spaces inside as "STRING"', function() {
|
||||
var result = tokenize("{{ foo bar \"baz bat\" }}");
|
||||
var result = tokenize('{{ foo bar "baz bat" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz bat");
|
||||
shouldBeToken(result[3], 'STRING', 'baz bat');
|
||||
});
|
||||
|
||||
it('tokenizes String params with escapes quotes as STRING', function() {
|
||||
var result = tokenize('{{ foo "bar\\"baz" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "STRING", 'bar"baz');
|
||||
shouldBeToken(result[2], 'STRING', 'bar"baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params using single quotes with escapes quotes as STRING', function() {
|
||||
var result = tokenize("{{ foo 'bar\\'baz' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "STRING", "bar'baz");
|
||||
shouldBeToken(result[2], 'STRING', "bar'baz");
|
||||
});
|
||||
|
||||
it('tokenizes numbers', function() {
|
||||
var result = tokenize('{{ foo 1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "NUMBER", "1");
|
||||
shouldBeToken(result[2], 'NUMBER', '1');
|
||||
|
||||
result = tokenize('{{ foo 1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "NUMBER", "1.1");
|
||||
shouldBeToken(result[2], 'NUMBER', '1.1');
|
||||
|
||||
result = tokenize('{{ foo -1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "NUMBER", "-1");
|
||||
shouldBeToken(result[2], 'NUMBER', '-1');
|
||||
|
||||
result = tokenize('{{ foo -1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "NUMBER", "-1.1");
|
||||
shouldBeToken(result[2], 'NUMBER', '-1.1');
|
||||
});
|
||||
|
||||
it('tokenizes booleans', function() {
|
||||
var result = tokenize('{{ foo true }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "true");
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'true');
|
||||
|
||||
result = tokenize('{{ foo false }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "false");
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'false');
|
||||
});
|
||||
|
||||
it('tokenizes undefined and null', function() {
|
||||
var result = tokenize('{{ foo undefined null }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'UNDEFINED', 'NULL', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'UNDEFINED', 'undefined');
|
||||
shouldBeToken(result[3], 'NULL', 'null');
|
||||
});
|
||||
|
||||
it('tokenizes hash arguments', function() {
|
||||
var result = tokenize("{{ foo bar=baz }}");
|
||||
var result = tokenize('{{ foo bar=baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=bat }}");
|
||||
result = tokenize('{{ foo bar baz=bat }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}");
|
||||
result = tokenize('{{ foo bar baz=1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}");
|
||||
result = tokenize('{{ foo bar baz=true }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}");
|
||||
result = tokenize('{{ foo bar baz=false }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}");
|
||||
result = tokenize('{{ foo bar\n baz=bat }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}");
|
||||
result = tokenize('{{ foo bar baz=\"bat\" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}");
|
||||
result = tokenize('{{ foo bar baz=\"bat\" bam=wot }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}");
|
||||
result = tokenize('{{foo omg bar=baz bat=\"bam\"}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "ID", "omg");
|
||||
shouldBeToken(result[2], 'ID', 'omg');
|
||||
});
|
||||
|
||||
it('tokenizes special @ identifiers', function() {
|
||||
var result = tokenize("{{ @foo }}");
|
||||
var result = tokenize('{{ @foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[2], "ID", "foo");
|
||||
shouldBeToken(result[2], 'ID', 'foo');
|
||||
|
||||
result = tokenize("{{ foo @bar }}");
|
||||
result = tokenize('{{ foo @bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize("{{ foo bar=@baz }}");
|
||||
result = tokenize('{{ foo bar=@baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[5], "ID", "baz");
|
||||
shouldBeToken(result[5], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('does not time out in a mustache with a single } followed by EOF', function() {
|
||||
shouldMatchTokens(tokenize("{{foo}"), ['OPEN', 'ID']);
|
||||
shouldMatchTokens(tokenize('{{foo}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('does not time out in a mustache when invalid ID characters are used', function() {
|
||||
shouldMatchTokens(tokenize("{{foo & }}"), ['OPEN', 'ID']);
|
||||
shouldMatchTokens(tokenize('{{foo & }}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('tokenizes subexpressions', function() {
|
||||
var result = tokenize("{{foo (bar)}}");
|
||||
var result = tokenize('{{foo (bar)}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize("{{foo (a-x b-y)}}");
|
||||
result = tokenize('{{foo (a-x b-y)}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[3], "ID", "a-x");
|
||||
shouldBeToken(result[4], "ID", "b-y");
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'a-x');
|
||||
shouldBeToken(result[4], 'ID', 'b-y');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions', function() {
|
||||
var result = tokenize("{{foo (bar (lol rofl)) (baz)}}");
|
||||
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
shouldBeToken(result[5], "ID", "lol");
|
||||
shouldBeToken(result[6], "ID", "rofl");
|
||||
shouldBeToken(result[10], "ID", "baz");
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
shouldBeToken(result[5], 'ID', 'lol');
|
||||
shouldBeToken(result[6], 'ID', 'rofl');
|
||||
shouldBeToken(result[10], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions: literals', function() {
|
||||
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes block params', function() {
|
||||
var result = tokenize('{{#foo as |bar|}}');
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{#foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{#foo as | bar baz |}}');
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{#foo as as | bar baz |}}');
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{else foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE_CHAIN', 'ID', 'OPEN_BLOCK_PARAMS', 'ID', 'ID', 'CLOSE_BLOCK_PARAMS', 'CLOSE']);
|
||||
});
|
||||
});
|
||||
|
||||
+36
-8
@@ -1,4 +1,3 @@
|
||||
/*global CompilerContext */
|
||||
describe('track ids', function() {
|
||||
var context;
|
||||
beforeEach(function() {
|
||||
@@ -27,7 +26,7 @@ describe('track ids', function() {
|
||||
equal(options.ids[0], 'is.a');
|
||||
equal(options.ids[1], 'slave.driver');
|
||||
|
||||
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +40,7 @@ describe('track ids', function() {
|
||||
equal(options.hashIds.bat, 'is.a');
|
||||
equal(options.hashIds.baz, 'slave.driver');
|
||||
|
||||
return "HELP ME MY BOSS " + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz;
|
||||
return 'HELP ME MY BOSS ' + options.hashIds.bat + ':' + options.hash.bat + ' ' + options.hashIds.baz + ':' + options.hash.baz;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,7 +54,7 @@ describe('track ids', function() {
|
||||
equal(options.ids[0], 'is.a');
|
||||
equal(options.ids[1], '../slave.driver');
|
||||
|
||||
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,11 +68,11 @@ describe('track ids', function() {
|
||||
equal(options.ids[0], '@is.a');
|
||||
equal(options.ids[1], '@slave.driver');
|
||||
|
||||
return "HELP ME MY BOSS " + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
equals(template({}, {helpers: helpers, data:context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar');
|
||||
equals(template({}, {helpers: helpers, data: context}), 'HELP ME MY BOSS @is.a:foo @slave.driver:bar');
|
||||
});
|
||||
|
||||
it('should return null for constants', function() {
|
||||
@@ -85,7 +84,7 @@ describe('track ids', function() {
|
||||
equal(options.ids[1], null);
|
||||
equal(options.hashIds.key, null);
|
||||
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun + ' ' + options.hash.key;
|
||||
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun + ' ' + options.hash.key;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,15 +98,39 @@ describe('track ids', function() {
|
||||
wycats: function(passiveVoice, options) {
|
||||
equal(options.ids[0], true);
|
||||
|
||||
return "HELP ME MY BOSS " + passiveVoice;
|
||||
return 'HELP ME MY BOSS ' + passiveVoice;
|
||||
}
|
||||
};
|
||||
|
||||
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1');
|
||||
});
|
||||
|
||||
it('should use block param paths', function() {
|
||||
var template = CompilerContext.compile('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}', {trackIds: true});
|
||||
|
||||
var helpers = {
|
||||
doIt: function(options) {
|
||||
var blockParams = [this.is];
|
||||
blockParams.path = ['zomg'];
|
||||
return options.fn(this, {blockParams: blockParams});
|
||||
},
|
||||
wycats: function(passiveVoice, noun, blah, options) {
|
||||
equal(options.ids[0], 'zomg.a');
|
||||
equal(options.ids[1], 'slave.driver');
|
||||
equal(options.ids[2], 'zomg');
|
||||
|
||||
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS zomg.a:foo slave.driver:bar');
|
||||
});
|
||||
|
||||
describe('builtin helpers', function() {
|
||||
var helpers = {
|
||||
blockParams: function(name, options) {
|
||||
return name + ':' + options.ids[0] + '\n';
|
||||
},
|
||||
wycats: function(name, options) {
|
||||
return name + ':' + options.data.contextPath + '\n';
|
||||
}
|
||||
@@ -129,6 +152,11 @@ describe('track ids', function() {
|
||||
|
||||
equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n');
|
||||
});
|
||||
it('should handle block params', function() {
|
||||
var template = CompilerContext.compile('{{#each array as |value|}}{{blockParams value.name}}{{/each}}', {trackIds: true});
|
||||
|
||||
equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0.name\nbar:array.1.name\n');
|
||||
});
|
||||
});
|
||||
describe('#with', function() {
|
||||
it('should track contextPath', function() {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars.template, true);
|
||||
equal(!!Handlebars.VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
|
||||
require(['handlebars'], function(Handlebars) {
|
||||
window.Handlebars = Handlebars;
|
||||
|
||||
require(['tests'], function() {
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+29
-9
@@ -1,19 +1,17 @@
|
||||
/*global Handlebars, shouldCompileTo */
|
||||
|
||||
describe('utils', function() {
|
||||
describe('#SafeString', function() {
|
||||
it("constructing a safestring from a string and checking its type", function() {
|
||||
var safe = new Handlebars.SafeString("testing 1, 2, 3");
|
||||
it('constructing a safestring from a string and checking its type', function() {
|
||||
var safe = new Handlebars.SafeString('testing 1, 2, 3');
|
||||
if (!(safe instanceof Handlebars.SafeString)) {
|
||||
throw new Error('Must be instance of SafeString');
|
||||
}
|
||||
equals(safe == 'testing 1, 2, 3', true, 'SafeString is equivalent to its underlying string');
|
||||
equals(safe.toString(), 'testing 1, 2, 3', 'SafeString is equivalent to its underlying string');
|
||||
});
|
||||
|
||||
it("it should not escape SafeString properties", function() {
|
||||
var name = new Handlebars.SafeString("<em>Sean O'Malley</em>");
|
||||
it('it should not escape SafeString properties', function() {
|
||||
var name = new Handlebars.SafeString('<em>Sean O'Malley</em>');
|
||||
|
||||
shouldCompileTo('{{name}}', [{ name: name }], "<em>Sean O'Malley</em>");
|
||||
shouldCompileTo('{{name}}', [{name: name}], '<em>Sean O'Malley</em>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,13 +23,19 @@ describe('utils', function() {
|
||||
var string = new Handlebars.SafeString('foo<&"\'>');
|
||||
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
|
||||
|
||||
var obj = {
|
||||
toHTML: function() {
|
||||
return 'foo<&"\'>';
|
||||
}
|
||||
};
|
||||
equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>');
|
||||
});
|
||||
it('should handle falsy', function() {
|
||||
equals(Handlebars.Utils.escapeExpression(''), '');
|
||||
equals(Handlebars.Utils.escapeExpression(undefined), '');
|
||||
equals(Handlebars.Utils.escapeExpression(null), '');
|
||||
equals(Handlebars.Utils.escapeExpression(false), '');
|
||||
|
||||
equals(Handlebars.Utils.escapeExpression(false), 'false');
|
||||
equals(Handlebars.Utils.escapeExpression(0), '0');
|
||||
});
|
||||
it('should handle empty objects', function() {
|
||||
@@ -56,4 +60,20 @@ describe('utils', function() {
|
||||
equals(Handlebars.Utils.isEmpty({bar: 1}), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#extend', function() {
|
||||
it('should ignore prototype values', function() {
|
||||
function A() {
|
||||
this.a = 1;
|
||||
}
|
||||
A.prototype.b = 4;
|
||||
|
||||
var b = {b: 2};
|
||||
|
||||
Handlebars.Utils.extend(b, new A());
|
||||
|
||||
equals(b.a, 1);
|
||||
equals(b.b, 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
describe('Visitor', function() {
|
||||
if (!Handlebars.Visitor || !Handlebars.print) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should provide coverage', function() {
|
||||
// Simply run the thing and make sure it does not fail and that all of the
|
||||
// stub methods are executed
|
||||
var visitor = new Handlebars.Visitor();
|
||||
visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'));
|
||||
});
|
||||
|
||||
it('should traverse to stubs', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.StringLiteral = function(string) {
|
||||
equal(string.value, '2');
|
||||
};
|
||||
visitor.NumberLiteral = function(number) {
|
||||
equal(number.value, 1);
|
||||
};
|
||||
visitor.BooleanLiteral = function(bool) {
|
||||
equal(bool.value, true);
|
||||
|
||||
equal(this.parents.length, 3);
|
||||
equal(this.parents[0].type, 'SubExpression');
|
||||
equal(this.parents[1].type, 'BlockStatement');
|
||||
equal(this.parents[2].type, 'Program');
|
||||
};
|
||||
visitor.PathExpression = function(id) {
|
||||
equal(/(foo\.)?bar$/.test(id.original), true);
|
||||
};
|
||||
visitor.ContentStatement = function(content) {
|
||||
equal(content.value, ' ');
|
||||
};
|
||||
visitor.CommentStatement = function(comment) {
|
||||
equal(comment.value, 'comment');
|
||||
};
|
||||
|
||||
visitor.accept(Handlebars.parse('{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'));
|
||||
});
|
||||
|
||||
it('should return undefined');
|
||||
|
||||
describe('mutating', function() {
|
||||
describe('fields', function() {
|
||||
it('should replace value', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.StringLiteral = function(string) {
|
||||
return new Handlebars.AST.NumberLiteral(42, string.locInfo);
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo foo="foo"}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
|
||||
});
|
||||
it('should treat undefined resonse as identity', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
visitor.mutating = true;
|
||||
|
||||
var ast = Handlebars.parse('{{foo foo=42}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n');
|
||||
});
|
||||
it('should remove false responses', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.Hash = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo foo=42}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
|
||||
});
|
||||
it('should throw when removing required values', function() {
|
||||
shouldThrow(function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.PathExpression = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo 42}}');
|
||||
visitor.accept(ast);
|
||||
}, Handlebars.Exception, 'MustacheStatement requires path');
|
||||
});
|
||||
it('should throw when returning non-node responses', function() {
|
||||
shouldThrow(function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.PathExpression = function() {
|
||||
return {};
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo 42}}');
|
||||
visitor.accept(ast);
|
||||
}, Handlebars.Exception, 'Unexpected node type "undefined" found when accepting path on MustacheStatement');
|
||||
});
|
||||
});
|
||||
describe('arrays', function() {
|
||||
it('should replace value', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.StringLiteral = function(string) {
|
||||
return new Handlebars.AST.NumberLiteral(42, string.locInfo);
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo "foo"}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
|
||||
});
|
||||
it('should treat undefined resonse as identity', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
visitor.mutating = true;
|
||||
|
||||
var ast = Handlebars.parse('{{foo 42}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
|
||||
});
|
||||
it('should remove false responses', function() {
|
||||
var visitor = new Handlebars.Visitor();
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.NumberLiteral = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo 42}}');
|
||||
visitor.accept(ast);
|
||||
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,8 @@ describe('whitespace control', function() {
|
||||
|
||||
shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
|
||||
shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
|
||||
|
||||
shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4');
|
||||
});
|
||||
|
||||
describe('blocks', function() {
|
||||
@@ -18,6 +20,9 @@ describe('whitespace control', function() {
|
||||
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
|
||||
|
||||
shouldCompileTo(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
|
||||
shouldCompileTo(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', hash, ' abara ');
|
||||
});
|
||||
it('should strip whitespace around inverse block calls', function() {
|
||||
var hash = {};
|
||||
@@ -26,6 +31,8 @@ describe('whitespace control', function() {
|
||||
shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
|
||||
|
||||
shouldCompileTo(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
|
||||
});
|
||||
it('should strip whitespace around complex block calls', function() {
|
||||
var hash = {foo: 'bar<'};
|
||||
@@ -37,6 +44,9 @@ describe('whitespace control', function() {
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
|
||||
|
||||
shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar');
|
||||
shouldCompileTo('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar<');
|
||||
|
||||
hash = {};
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
|
||||
@@ -45,6 +55,8 @@ describe('whitespace control', function() {
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
|
||||
|
||||
shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'baz');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,6 +64,9 @@ describe('whitespace control', function() {
|
||||
shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
|
||||
shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar');
|
||||
shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar ');
|
||||
|
||||
shouldCompileToWithPartials('foo\n {{~> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
|
||||
shouldCompileToWithPartials('foo\n {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo\n bar');
|
||||
});
|
||||
|
||||
it('should only strip whitespace once', function() {
|
||||
|
||||
+23
-12
@@ -12,7 +12,7 @@ function strip(start, end) {
|
||||
LEFT_STRIP "~"
|
||||
RIGHT_STRIP "~"
|
||||
|
||||
LOOKAHEAD [=~}\s\/.)]
|
||||
LOOKAHEAD [=~}\s\/.)|]
|
||||
LITERAL_LOOKAHEAD [~}\s)]
|
||||
|
||||
/*
|
||||
@@ -28,7 +28,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
|
||||
%%
|
||||
|
||||
[^\x00]*?/("{{") {
|
||||
[^\x00]*?/("{{") {
|
||||
if(yytext.slice(-2) === "\\\\") {
|
||||
strip(0,1);
|
||||
this.begin("mu");
|
||||
@@ -56,7 +56,10 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
}
|
||||
<raw>[^\x00]*?/("{{{{/") { return 'CONTENT'; }
|
||||
|
||||
<com>[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT';
|
||||
<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
|
||||
this.popState();
|
||||
return 'COMMENT';
|
||||
}
|
||||
|
||||
<mu>"(" return 'OPEN_SEXPR';
|
||||
<mu>")" return 'CLOSE_SEXPR';
|
||||
@@ -67,20 +70,24 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
this.begin('raw');
|
||||
return 'CLOSE_RAW_BLOCK';
|
||||
}
|
||||
<mu>"{{{{"[^\x00]*"}}}}" {
|
||||
yytext = yytext.substr(4, yyleng-8);
|
||||
this.popState();
|
||||
return 'RAW_BLOCK';
|
||||
}
|
||||
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
|
||||
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN';
|
||||
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
|
||||
<mu>"{{"{LEFT_STRIP}?"&" return 'OPEN';
|
||||
<mu>"{{!--" this.popState(); this.begin('com');
|
||||
<mu>"{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT';
|
||||
<mu>"{{"{LEFT_STRIP}?"!--" {
|
||||
this.unput(yytext);
|
||||
this.popState();
|
||||
this.begin('com');
|
||||
}
|
||||
<mu>"{{"{LEFT_STRIP}?"!"[\s\S]*?"}}" {
|
||||
this.popState();
|
||||
return 'COMMENT';
|
||||
}
|
||||
<mu>"{{"{LEFT_STRIP}? return 'OPEN';
|
||||
|
||||
<mu>"=" return 'EQUALS';
|
||||
@@ -95,11 +102,15 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
<mu>"@" return 'DATA';
|
||||
<mu>"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
|
||||
<mu>"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
|
||||
<mu>"undefined"/{LITERAL_LOOKAHEAD} return 'UNDEFINED';
|
||||
<mu>"null"/{LITERAL_LOOKAHEAD} return 'NULL';
|
||||
<mu>\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER';
|
||||
<mu>"as"\s+"|" return 'OPEN_BLOCK_PARAMS';
|
||||
<mu>"|" return 'CLOSE_BLOCK_PARAMS';
|
||||
|
||||
<mu>{ID} return 'ID';
|
||||
|
||||
<mu>'['[^\]]*']' yytext = strip(1,2); return 'ID';
|
||||
<mu>'['[^\]]*']' return 'ID';
|
||||
<mu>. return 'INVALID';
|
||||
|
||||
<INITIAL,mu><<EOF>> return 'EOF';
|
||||
|
||||
+74
-63
@@ -2,118 +2,129 @@
|
||||
|
||||
%ebnf
|
||||
|
||||
%{
|
||||
|
||||
function stripFlags(open, close) {
|
||||
return {
|
||||
left: open.charAt(2) === '~',
|
||||
right: close.charAt(0) === '~' || close.charAt(1) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
root
|
||||
: statements EOF { return new yy.ProgramNode($1, @$); }
|
||||
| EOF { return new yy.ProgramNode([], @$); }
|
||||
: program EOF { return $1; }
|
||||
;
|
||||
|
||||
program
|
||||
: simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
|
||||
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
|
||||
| statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
|
||||
| statements -> new yy.ProgramNode($1, @$)
|
||||
| simpleInverse -> new yy.ProgramNode([], @$)
|
||||
| "" -> new yy.ProgramNode([], @$)
|
||||
;
|
||||
|
||||
statements
|
||||
: statement -> [$1]
|
||||
| statements statement { $1.push($2); $$ = $1; }
|
||||
: statement* -> new yy.Program($1, null, {}, yy.locInfo(@$))
|
||||
;
|
||||
|
||||
statement
|
||||
: openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
|
||||
| openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
|
||||
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
|
||||
| mustache -> $1
|
||||
: mustache -> $1
|
||||
| block -> $1
|
||||
| rawBlock -> $1
|
||||
| partial -> $1
|
||||
| CONTENT -> new yy.ContentNode($1, @$)
|
||||
| COMMENT -> new yy.CommentNode($1, @$)
|
||||
| content -> $1
|
||||
| COMMENT -> new yy.CommentStatement(yy.stripComment($1), yy.stripFlags($1, $1), yy.locInfo(@$))
|
||||
;
|
||||
|
||||
content
|
||||
: CONTENT -> new yy.ContentStatement($1, yy.locInfo(@$))
|
||||
;
|
||||
|
||||
rawBlock
|
||||
: openRawBlock content END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
|
||||
;
|
||||
|
||||
openRawBlock
|
||||
: OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$)
|
||||
: OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK -> { path: $2, params: $3, hash: $4 }
|
||||
;
|
||||
|
||||
block
|
||||
: openBlock program inverseChain? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
|
||||
| openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
|
||||
;
|
||||
|
||||
openInverse
|
||||
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
: OPEN_INVERSE helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
|
||||
;
|
||||
|
||||
openInverseChain
|
||||
: OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
|
||||
;
|
||||
|
||||
inverseAndProgram
|
||||
: INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
|
||||
;
|
||||
|
||||
inverseChain
|
||||
: openInverseChain program inverseChain? {
|
||||
var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$),
|
||||
program = new yy.Program([inverse], null, {}, yy.locInfo(@$));
|
||||
program.chained = true;
|
||||
|
||||
$$ = { strip: $1.strip, program: program, chain: true };
|
||||
}
|
||||
| inverseAndProgram -> $1
|
||||
;
|
||||
|
||||
closeBlock
|
||||
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)}
|
||||
: OPEN_ENDBLOCK helperName CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)}
|
||||
;
|
||||
|
||||
mustache
|
||||
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
|
||||
// This also allows for handler unification as all mustache node instances can utilize the same handler
|
||||
: OPEN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
| OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
|
||||
: OPEN helperName param* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
|
||||
| OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
|
||||
;
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$)
|
||||
| OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$)
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
|
||||
;
|
||||
|
||||
sexpr
|
||||
: path param* hash? -> new yy.SexprNode([$1].concat($2), $3, @$)
|
||||
| dataName -> new yy.SexprNode([$1], null, @$)
|
||||
: OPEN_PARTIAL partialName param* hash? CLOSE -> new yy.PartialStatement($2, $3, $4, yy.stripFlags($1, $5), yy.locInfo(@$))
|
||||
;
|
||||
|
||||
param
|
||||
: path -> $1
|
||||
| STRING -> new yy.StringNode($1, @$)
|
||||
| NUMBER -> new yy.NumberNode($1, @$)
|
||||
| BOOLEAN -> new yy.BooleanNode($1, @$)
|
||||
| dataName -> $1
|
||||
| OPEN_SEXPR sexpr CLOSE_SEXPR {$2.isHelper = true; $$ = $2;}
|
||||
: helperName -> $1
|
||||
| sexpr -> $1
|
||||
;
|
||||
|
||||
sexpr
|
||||
: OPEN_SEXPR helperName param* hash? CLOSE_SEXPR -> new yy.SubExpression($2, $3, $4, yy.locInfo(@$))
|
||||
;
|
||||
|
||||
hash
|
||||
: hashSegment+ -> new yy.HashNode($1, @$)
|
||||
: hashSegment+ -> new yy.Hash($1, yy.locInfo(@$))
|
||||
;
|
||||
|
||||
hashSegment
|
||||
: ID EQUALS param -> [$1, $3]
|
||||
: ID EQUALS param -> new yy.HashPair(yy.id($1), $3, yy.locInfo(@$))
|
||||
;
|
||||
|
||||
blockParams
|
||||
: OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2)
|
||||
;
|
||||
|
||||
helperName
|
||||
: path -> $1
|
||||
| dataName -> $1
|
||||
| STRING -> new yy.StringLiteral($1, yy.locInfo(@$))
|
||||
| NUMBER -> new yy.NumberLiteral($1, yy.locInfo(@$))
|
||||
| BOOLEAN -> new yy.BooleanLiteral($1, yy.locInfo(@$))
|
||||
| UNDEFINED -> new yy.UndefinedLiteral(yy.locInfo(@$))
|
||||
| NULL -> new yy.NullLiteral(yy.locInfo(@$))
|
||||
;
|
||||
|
||||
partialName
|
||||
: path -> new yy.PartialNameNode($1, @$)
|
||||
| STRING -> new yy.PartialNameNode(new yy.StringNode($1, @$), @$)
|
||||
| NUMBER -> new yy.PartialNameNode(new yy.NumberNode($1, @$))
|
||||
: helperName -> $1
|
||||
| sexpr -> $1
|
||||
;
|
||||
|
||||
dataName
|
||||
: DATA path -> new yy.DataNode($2, @$)
|
||||
: DATA pathSegments -> yy.preparePath(true, $2, @$)
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments -> new yy.IdNode($1, @$)
|
||||
: pathSegments -> yy.preparePath(false, $1, @$)
|
||||
;
|
||||
|
||||
pathSegments
|
||||
: pathSegments SEP ID { $1.push({part: $3, separator: $2}); $$ = $1; }
|
||||
| ID -> [{part: $1}]
|
||||
: pathSegments SEP ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; }
|
||||
| ID -> [{part: yy.id($1), original: $1}]
|
||||
;
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
/* jshint ignore:start */
|
||||
/* istanbul ignore next */
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export default handlebars;
|
||||
/* jshint ignore:end */
|
||||
|
||||
+12
-1
@@ -29,5 +29,16 @@ module.exports = function(grunt) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test', ['test:bin', 'test:mocha']);
|
||||
grunt.registerTask('test:cov', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--', './spec/env/runner.js'], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal(code + ' tests failed');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||
};
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ module.exports = function(grunt) {
|
||||
grunt.log.writeln('Updating to version ' + version);
|
||||
|
||||
async.each([
|
||||
['lib/handlebars/base.js', /var VERSION = "(.*)";/, 'var VERSION = "' + version + '";'],
|
||||
['lib/handlebars/base.js', /var VERSION = ['"](.*)['"];/, 'var VERSION = "' + version + '";'],
|
||||
['components/bower.json', /"version":.*/, '"version": "' + version + '",'],
|
||||
['components/handlebars.js.nuspec', /<version>.*<\/version>/, '<version>' + version + '</version>']
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user