diff --git a/Gruntfile.js b/Gruntfile.js index 528c0af82..ac9bf3f8d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -177,19 +177,19 @@ module.exports = function(grunt) { concurrency: 4, browsers: [ { browserName: 'chrome' }, - { browserName: 'firefox', platform: 'Linux' }, + { browserName: 'firefox', platform: 'Linux' } // {browserName: 'safari', version: 9, platform: 'OS X 10.11'}, // {browserName: 'safari', version: 8, platform: 'OS X 10.10'}, - { - browserName: 'internet explorer', - version: 11, - platform: 'Windows 8.1' - }, - { - browserName: 'internet explorer', - version: 10, - platform: 'Windows 8' - } + // { + // browserName: 'internet explorer', + // version: 11, + // platform: 'Windows 8.1' + // }, + // { + // browserName: 'internet explorer', + // version: 10, + // platform: 'Windows 8' + // } ] } }, diff --git a/components/bower.json b/components/bower.json index 788826d07..ad03dcc42 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.7.6", + "version": "4.7.7", "main": "handlebars.js", "license": "MIT", "dependencies": {} diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index 4594e2512..8f82ec85c 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 4.7.6 + 4.7.7 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/components/package.json b/components/package.json index ecb9960da..1f8e7309c 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.7.6", + "version": "4.7.7", "license": "MIT", "jspm": { "main": "handlebars", diff --git a/integration-testing/multi-nodejs-test/test.sh b/integration-testing/multi-nodejs-test/test.sh index 99682b453..d16361dfb 100755 --- a/integration-testing/multi-nodejs-test/test.sh +++ b/integration-testing/multi-nodejs-test/test.sh @@ -12,18 +12,20 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1 # However, the built distribution should work with older NodeJS versions as well. # This test is simple by design. It merely ensures, that calling Handlebars does not fail with old versions. # It does (almost) not test for correctness, because that is already done in the mocha-tests. -# And it does not use any NodeJS based testing framwork to make this part independent of the Node version. +# And it does not use any NodeJS based testing framework to make this part independent of the Node version. unset npm_config_prefix echo "Handlebars should be able to run in various versions of NodeJS" -for i in 0.10 0.12 4 5 6 7 8 9 10 11 ; do +for node_version_to_test in 0.10 0.12 4 5 6 7 8 9 10 11 12 13 14 15; do + rm target node_modules package-lock.json -rf mkdir target - nvm install "$i" - nvm exec "$i" npm install - nvm exec "$i" npm run test - nvm exec "$i" npm run test-precompile + + nvm install "$node_version_to_test" + nvm exec "$node_version_to_test" npm install + nvm exec "$node_version_to_test" npm run test + nvm exec "$node_version_to_test" npm run test-precompile echo Success done diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 753c9a466..13bc32b07 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -5,7 +5,7 @@ import { registerDefaultDecorators } from './decorators'; import logger from './logger'; import { resetLoggedProperties } from './internal/proto-access'; -export const VERSION = '4.7.6'; +export const VERSION = '4.7.7'; export const COMPILER_REVISION = 8; export const LAST_COMPATIBLE_COMPILER_REVISION = 7; diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 63f90bdc2..db1c50c62 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -16,7 +16,12 @@ JavaScriptCompiler.prototype = { return this.internalNameLookup(parent, name); }, depthedLookup: function(name) { - return [this.aliasable('container.lookup'), '(depths, "', name, '")']; + return [ + this.aliasable('container.lookup'), + '(depths, ', + JSON.stringify(name), + ')' + ]; }, compilerInfo: function() { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 67a500d79..36bf6c94d 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -124,7 +124,7 @@ export function template(templateSpec, env) { loc: loc }); } - return obj[name]; + return container.lookupProperty(obj, name); }, lookupProperty: function(parent, propertyName) { let result = parent[propertyName]; diff --git a/package-lock.json b/package-lock.json index 28b4eb763..c9abea56d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.7.5", + "version": "4.7.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2826,7 +2826,7 @@ "request": "^2.88.0", "strip-json-comments": "^2.0.1", "tslint": "^5.14.0", - "typescript": "^3.9.0-dev.20200402" + "typescript": "^4.3.0-dev.20210213" }, "dependencies": { "fs-extra": { @@ -2841,9 +2841,9 @@ } }, "typescript": { - "version": "3.9.0-dev.20200402", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200402.tgz", - "integrity": "sha512-CxOOy4lmaPnuyG34aP1kF2l++aou/IM+T0XsEeXZWb6xbIwx+3rt1DbLNS0pQIsLxi7NITq3x4M1qXhOQOAE6A==", + "version": "4.3.0-dev.20210213", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.0-dev.20210213.tgz", + "integrity": "sha512-zJG5QIkviTzzYRmKylVXIG2YTV7P18rKLclZu67WypYhsPtaC3KSDD5mOiXJsurDw5rqTqN/OgjQ37cpc11P3A==", "dev": true } } @@ -3904,628 +3904,14 @@ "dev": true }, "fsevents": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", - "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, "optional": true, "requires": { "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", - "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "optional": true - } + "nan": "^2.12.1" } }, "functional-red-black-tree": { @@ -7636,9 +7022,9 @@ "dev": true }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true, "optional": true }, @@ -7821,9 +7207,6 @@ "dev": true, "requires": { "underscore": ">= 1.1.5" - }, - "dependencies": { - "browserify": {} } }, "nopt": { diff --git a/package.json b/package.json index aa5c2dff6..ffeaf7d29 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.7.6", + "version": "4.7.7", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [ diff --git a/release-notes.md b/release-notes.md index fa945d3a5..cabef32b1 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,26 @@ ## Development -[Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...master) +[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...master) + +## v4.7.7 - February 15th, 2021 + +- fix weird error in integration tests - eb860c0 +- fix: check prototype property access in strict-mode (#1736) - b6d3de7 +- fix: escape property names in compat mode (#1736) - f058970 +- refactor: In spec tests, use expectTemplate over equals and shouldThrow (#1683) - 77825f8 +- chore: start testing on Node.js 12 and 13 - 3789a30 + +(POSSIBLY) BREAKING CHANGES: + +- the changes from version [4.6.0](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md#v460---january-8th-2020) now also apply + in when using the compile-option "strict: true". Access to prototype properties is forbidden completely by default, specific properties or methods + can be allowed via runtime-options. See #1633 for details. If you are using Handlebars as documented, you should not be accessing prototype properties + from your template anyway, so the changes should not be a problem for you. Only the use of undocumented features can break your build. + +That is why we only bump the patch version despite mentioning breaking changes. + +[Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7) ## v4.7.6 - April 3rd, 2020 diff --git a/spec/basic.js b/spec/basic.js index 65d228729..4c7afb706 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -6,117 +6,156 @@ beforeEach(function() { describe('basic context', function() { it('most basic', function() { - shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo'); + expectTemplate('{{foo}}') + .withInput({ foo: 'foo' }) + .toCompileTo('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'); + expectTemplate('\\{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('{{foo}}'); + + expectTemplate('content \\{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('content {{foo}}'); + + expectTemplate('\\\\{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('\\food'); + + expectTemplate('content \\\\{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('content \\food'); + + expectTemplate('\\\\ {{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('\\\\ 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' - ); + expectTemplate('Goodbye\n{{cruel}}\n{{world}}!') + .withInput({ + cruel: 'cruel', + world: 'world' + }) + .withMessage('It works if all the required keys are provided') + .toCompileTo('Goodbye\ncruel\nworld!'); }); it('compiling with a string context', function() { - shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3'); + expectTemplate('{{.}}{{length}}') + .withInput('bye') + .toCompileTo('bye3'); }); it('compiling with an undefined context', function() { - shouldCompileTo( - 'Goodbye\n{{cruel}}\n{{world.bar}}!', - undefined, - 'Goodbye\n\n!' - ); + expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') + .withInput(undefined) + .toCompileTo('Goodbye\n\n!'); - shouldCompileTo( - '{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', - undefined, - 'Goodbye' - ); + expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}') + .withInput(undefined) + .toCompileTo('Goodbye'); }); it('comments', function() { - shouldCompileTo( - '{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!', - { cruel: 'cruel', world: 'world' }, - 'Goodbye\ncruel\nworld!', - 'comments are ignored' + expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!') + .withInput({ + cruel: 'cruel', + world: 'world' + }) + .withMessage('comments are ignored') + .toCompileTo('Goodbye\ncruel\nworld!'); + + expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah'); + + expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo( + 'blah' ); - 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'); - }); + expectTemplate(' {{! comment ~}} blah').toCompileTo(' 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' + expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo( + ' blah' ); - shouldCompileTo( - string, - { goodbye: false, world: 'world' }, - 'cruel world!', - 'booleans do not show the contents when false' + expectTemplate(' {{~! comment}} blah').toCompileTo(' blah'); + + expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo( + ' blah' ); }); + it('boolean', function() { + var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; + expectTemplate(string) + .withInput({ + goodbye: true, + world: 'world' + }) + .withMessage('booleans show the contents when true') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: false, + world: 'world' + }) + .withMessage('booleans do not show the contents when false') + .toCompileTo('cruel world!'); + }); + 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'); + expectTemplate('num1: {{num1}}, num2: {{num2}}') + .withInput({ + num1: 42, + num2: 0 + }) + .toCompileTo('num1: 42, num2: 0'); + + expectTemplate('num: {{.}}') + .withInput(0) + .toCompileTo('num: 0'); + + expectTemplate('num: {{num1/num2}}') + .withInput({ num1: { num2: 0 } }) + .toCompileTo('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' - ); + expectTemplate('val1: {{val1}}, val2: {{val2}}') + .withInput({ + val1: false, + val2: new Boolean(false) + }) + .toCompileTo('val1: false, val2: false'); + + expectTemplate('val: {{.}}') + .withInput(false) + .toCompileTo('val: false'); + + expectTemplate('val: {{val1/val2}}') + .withInput({ val1: { val2: false } }) + .toCompileTo('val: false'); + + expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}') + .withInput({ + val1: false, + val2: new Boolean(false) + }) + .toCompileTo('val1: false, val2: false'); + + expectTemplate('val: {{{val1/val2}}}') + .withInput({ val1: { val2: false } }) + .toCompileTo('val: false'); /* eslint-enable */ }); it('should handle undefined and null', function() { - shouldCompileTo( - '{{awesome undefined null}}', - { + expectTemplate('{{awesome undefined null}}') + .withInput({ awesome: function(_undefined, _null, options) { return ( (_undefined === undefined) + @@ -126,373 +165,325 @@ describe('basic context', function() { typeof options ); } - }, - 'true true object' - ); - shouldCompileTo( - '{{undefined}}', - { + }) + .toCompileTo('true true object'); + + expectTemplate('{{undefined}}') + .withInput({ undefined: function() { return 'undefined!'; } - }, - 'undefined!' - ); - shouldCompileTo( - '{{null}}', - { + }) + .toCompileTo('undefined!'); + + expectTemplate('{{null}}') + .withInput({ null: function() { return 'null!'; } - }, - 'null!' - ); + }) + .toCompileTo('null!'); }); it('newlines', function() { - shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); - shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); + expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); + + expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); }); it('escaping text', function() { - shouldCompileTo( - "Awesome's", - {}, - "Awesome's", - "text is escaped so that it doesn't get caught on single quotes" - ); - shouldCompileTo( - 'Awesome\\', - {}, - 'Awesome\\', - "text is escaped so that the closing quote can't be ignored" - ); - shouldCompileTo( - 'Awesome\\\\ foo', - {}, - 'Awesome\\\\ foo', - "text is escaped so that it doesn't mess up backslashes" - ); - shouldCompileTo( - 'Awesome {{foo}}', - { foo: '\\' }, - 'Awesome \\', - "text is escaped so that it doesn't mess up backslashes" - ); - shouldCompileTo( - " ' ' ", - {}, - " ' ' ", - 'double quotes never produce invalid javascript' - ); + expectTemplate("Awesome's") + .withMessage( + "text is escaped so that it doesn't get caught on single quotes" + ) + .toCompileTo("Awesome's"); + + expectTemplate('Awesome\\') + .withMessage("text is escaped so that the closing quote can't be ignored") + .toCompileTo('Awesome\\'); + + expectTemplate('Awesome\\\\ foo') + .withMessage("text is escaped so that it doesn't mess up backslashes") + .toCompileTo('Awesome\\\\ foo'); + + expectTemplate('Awesome {{foo}}') + .withInput({ foo: '\\' }) + .withMessage("text is escaped so that it doesn't mess up backslashes") + .toCompileTo('Awesome \\'); + + expectTemplate(" ' ' ") + .withMessage('double quotes never produce invalid javascript') + .toCompileTo(" ' ' "); }); it('escaping expressions', function() { - shouldCompileTo( - '{{{awesome}}}', - { awesome: "&'\\<>" }, - "&'\\<>", - "expressions with 3 handlebars aren't escaped" - ); + expectTemplate('{{{awesome}}}') + .withInput({ awesome: "&'\\<>" }) + .withMessage("expressions with 3 handlebars aren't escaped") + .toCompileTo("&'\\<>"); - shouldCompileTo( - '{{&awesome}}', - { awesome: "&'\\<>" }, - "&'\\<>", - "expressions with {{& handlebars aren't escaped" - ); + expectTemplate('{{&awesome}}') + .withInput({ awesome: "&'\\<>" }) + .withMessage("expressions with {{& handlebars aren't escaped") + .toCompileTo("&'\\<>"); - shouldCompileTo( - '{{awesome}}', - { awesome: '&"\'`\\<>' }, - '&"'`\\<>', - 'by default expressions should be escaped' - ); + expectTemplate('{{awesome}}') + .withInput({ awesome: '&"\'`\\<>' }) + .withMessage('by default expressions should be escaped') + .toCompileTo('&"'`\\<>'); - shouldCompileTo( - '{{awesome}}', - { awesome: 'Escaped, looks like: <b>' }, - 'Escaped, <b> looks like: &lt;b&gt;', - 'escaping should properly handle amperstands' - ); + expectTemplate('{{awesome}}') + .withInput({ awesome: 'Escaped, looks like: <b>' }) + .withMessage('escaping should properly handle amperstands') + .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); }); it("functions returning safestrings shouldn't be escaped", function() { - var hash = { - awesome: function() { - return new Handlebars.SafeString("&'\\<>"); - } - }; - shouldCompileTo( - '{{awesome}}', - hash, - "&'\\<>", - "functions returning safestrings aren't escaped" - ); + expectTemplate('{{awesome}}') + .withInput({ + awesome: function() { + return new Handlebars.SafeString("&'\\<>"); + } + }) + .withMessage("functions returning safestrings aren't escaped") + .toCompileTo("&'\\<>"); }); it('functions', function() { - shouldCompileTo( - '{{awesome}}', - { + expectTemplate('{{awesome}}') + .withInput({ awesome: function() { return 'Awesome'; } - }, - 'Awesome', - 'functions are called and render their output' - ); - shouldCompileTo( - '{{awesome}}', - { + }) + .withMessage('functions are called and render their output') + .toCompileTo('Awesome'); + + expectTemplate('{{awesome}}') + .withInput({ awesome: function() { return this.more; }, more: 'More awesome' - }, - 'More awesome', - 'functions are bound to the context' - ); + }) + .withMessage('functions are bound to the context') + .toCompileTo('More awesome'); }); it('functions with context argument', function() { - shouldCompileTo( - '{{awesome frank}}', - { + expectTemplate('{{awesome frank}}') + .withInput({ awesome: function(context) { return context; }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); + it('pathed functions with context argument', function() { - shouldCompileTo( - '{{bar.awesome frank}}', - { + expectTemplate('{{bar.awesome frank}}') + .withInput({ bar: { awesome: function(context) { return context; } }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); + it('depthed functions with context argument', function() { - shouldCompileTo( - '{{#with frank}}{{../awesome .}}{{/with}}', - { + expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') + .withInput({ awesome: function(context) { return context; }, frank: 'Frank' - }, - 'Frank', - 'functions are called with context arguments' - ); + }) + .withMessage('functions are called with context arguments') + .toCompileTo('Frank'); }); it('block functions with context argument', function() { - shouldCompileTo( - '{{#awesome 1}}inner {{.}}{{/awesome}}', - { + expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') + .withInput({ awesome: function(context, options) { return options.fn(context); } - }, - 'inner 1', - 'block functions are called with context and options' - ); + }) + .withMessage('block functions are called with context and options') + .toCompileTo('inner 1'); }); it('depthed block functions with context argument', function() { - shouldCompileTo( - '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}', - { + expectTemplate( + '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' + ) + .withInput({ value: true, awesome: function(context, options) { return options.fn(context); } - }, - 'inner 1', - 'block functions are called with context and options' - ); + }) + .withMessage('block functions are called with context and options') + .toCompileTo('inner 1'); }); it('block functions without context argument', function() { - shouldCompileTo( - '{{#awesome}}inner{{/awesome}}', - { + expectTemplate('{{#awesome}}inner{{/awesome}}') + .withInput({ awesome: function(options) { return options.fn(this); } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); + it('pathed block functions without context argument', function() { - shouldCompileTo( - '{{#foo.awesome}}inner{{/foo.awesome}}', - { + expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') + .withInput({ foo: { awesome: function() { return this; } } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); + it('depthed block functions without context argument', function() { - shouldCompileTo( - '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}', - { + expectTemplate( + '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' + ) + .withInput({ value: true, awesome: function() { return this; } - }, - 'inner', - 'block functions are called with options' - ); + }) + .withMessage('block functions are called with options') + .toCompileTo('inner'); }); 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 (-)' - ); + expectTemplate('{{foo-bar}}') + .withInput({ 'foo-bar': 'baz' }) + .withMessage('Paths can contain hyphens (-)') + .toCompileTo('baz'); + + expectTemplate('{{foo.foo-bar}}') + .withInput({ foo: { 'foo-bar': 'baz' } }) + .withMessage('Paths can contain hyphens (-)') + .toCompileTo('baz'); + + expectTemplate('{{foo/foo-bar}}') + .withInput({ foo: { 'foo-bar': 'baz' } }) + .withMessage('Paths can contain hyphens (-)') + .toCompileTo('baz'); }); it('nested paths', function() { - shouldCompileTo( - 'Goodbye {{alan/expression}} world!', - { alan: { expression: 'beautiful' } }, - 'Goodbye beautiful world!', - 'Nested paths access nested objects' - ); + expectTemplate('Goodbye {{alan/expression}} world!') + .withInput({ alan: { expression: 'beautiful' } }) + .withMessage('Nested paths access nested objects') + .toCompileTo('Goodbye beautiful world!'); }); 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' - ); + expectTemplate('Goodbye {{alan/expression}} world!') + .withInput({ alan: { expression: '' } }) + .withMessage('Nested paths access nested objects with empty string') + .toCompileTo('Goodbye world!'); }); 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' - ); + expectTemplate('Goodbye {{[@alan]/expression}} world!') + .withInput({ '@alan': { expression: 'beautiful' } }) + .withMessage('Literal paths can be used') + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate('Goodbye {{[foo bar]/expression}} world!') + .withInput({ 'foo bar': { expression: 'beautiful' } }) + .withMessage('Literal paths can be used') + .toCompileTo('Goodbye beautiful world!'); }); it('literal references', function() { - shouldCompileTo( - 'Goodbye {{[foo bar]}} world!', - { 'foo bar': 'beautiful' }, - 'Goodbye beautiful world!' - ); - shouldCompileTo( - 'Goodbye {{"foo bar"}} world!', - { 'foo bar': 'beautiful' }, - 'Goodbye beautiful world!' - ); - shouldCompileTo( - "Goodbye {{'foo bar'}} world!", - { 'foo bar': 'beautiful' }, - 'Goodbye beautiful world!' - ); - shouldCompileTo( - 'Goodbye {{"foo[bar"}} world!', - { 'foo[bar': 'beautiful' }, - 'Goodbye beautiful world!' - ); - shouldCompileTo( - 'Goodbye {{"foo\'bar"}} world!', - { "foo'bar": 'beautiful' }, - 'Goodbye beautiful world!' - ); - shouldCompileTo( - "Goodbye {{'foo\"bar'}} world!", - { 'foo"bar': 'beautiful' }, - 'Goodbye beautiful world!' - ); + expectTemplate('Goodbye {{[foo bar]}} world!') + .withInput({ 'foo bar': 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate('Goodbye {{"foo bar"}} world!') + .withInput({ 'foo bar': 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate("Goodbye {{'foo bar'}} world!") + .withInput({ 'foo bar': 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate('Goodbye {{"foo[bar"}} world!') + .withInput({ 'foo[bar': 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate('Goodbye {{"foo\'bar"}} world!') + .withInput({ "foo'bar": 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); + + expectTemplate("Goodbye {{'foo\"bar'}} world!") + .withInput({ 'foo"bar': 'beautiful' }) + .toCompileTo('Goodbye beautiful world!'); }); it("that current context path ({{.}}) doesn't hit helpers", function() { - shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: '); + expectTemplate('test: {{.}}') + .withInput(null) + .withHelpers({ helper: 'awesome' }) + .toCompileTo('test: '); }); it('complex but empty paths', function() { - shouldCompileTo('{{person/name}}', { person: { name: null } }, ''); - shouldCompileTo('{{person/name}}', { person: {} }, ''); + expectTemplate('{{person/name}}') + .withInput({ person: { name: null } }) + .toCompileTo(''); + + expectTemplate('{{person/name}}') + .withInput({ person: {} }) + .toCompileTo(''); }); 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' - ); + expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') + .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) + .withMessage('This keyword in paths evaluates to current context') + .toCompileTo('goodbyeGoodbyeGOODBYE'); - string = '{{#hellos}}{{this/text}}{{/hellos}}'; - hash = { - hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] - }; - shouldCompileTo( - string, - hash, - 'helloHelloHELLO', - 'This keyword evaluates in more complex paths' - ); + expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') + .withInput({ + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + }) + .withMessage('This keyword evaluates in more complex paths') + .toCompileTo('helloHelloHELLO'); }); it('this keyword nested inside path', function() { - shouldThrow( - function() { - CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}'); - }, + expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:13' ); - shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar'); - shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar'); + expectTemplate('{{[this]}}') + .withInput({ this: 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{text/[this]}}') + .withInput({ text: { this: 'bar' } }) + .toCompileTo('bar'); }); it('this keyword in helpers', function() { @@ -501,108 +492,105 @@ describe('basic context', function() { 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' - ); - 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' - ); + expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}') + .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) + .withHelpers(helpers) + .withMessage('This keyword in paths evaluates to current context') + .toCompileTo('bar goodbyebar Goodbyebar GOODBYE'); + + expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}') + .withInput({ + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + }) + .withHelpers(helpers) + .withMessage('This keyword evaluates in more complex paths') + .toCompileTo('bar hellobar Hellobar HELLO'); }); it('this keyword nested inside helpers param', function() { - var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}'; - shouldThrow( - function() { - CompilerContext.compile(string); - }, + expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:17' ); - shouldCompileTo( - '{{foo [this]}}', - { + expectTemplate('{{foo [this]}}') + .withInput({ foo: function(value) { return value; }, this: 'bar' - }, - 'bar' - ); - shouldCompileTo( - '{{foo text/[this]}}', - { + }) + .toCompileTo('bar'); + + expectTemplate('{{foo text/[this]}}') + .withInput({ foo: function(value) { return value; }, text: { this: 'bar' } - }, - 'bar' - ); + }) + .toCompileTo('bar'); }); it('pass string literals', function() { - shouldCompileTo('{{"foo"}}', {}, ''); - shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar'); - shouldCompileTo( - '{{#"foo"}}{{.}}{{/"foo"}}', - { foo: ['bar', 'baz'] }, - 'barbaz' - ); + expectTemplate('{{"foo"}}').toCompileTo(''); + + expectTemplate('{{"foo"}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') + .withInput({ + foo: ['bar', 'baz'] + }) + .toCompileTo('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}}', - { + expectTemplate('{{12}}').toCompileTo(''); + + expectTemplate('{{12}}') + .withInput({ '12': 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{12.34}}').toCompileTo(''); + + expectTemplate('{{12.34}}') + .withInput({ '12.34': 'bar' }) + .toCompileTo('bar'); + + expectTemplate('{{12.34 1}}') + .withInput({ '12.34': function(arg) { return 'bar' + arg; } - }, - 'bar1' - ); + }) + .toCompileTo('bar1'); }); it('pass boolean literals', function() { - shouldCompileTo('{{true}}', {}, ''); - shouldCompileTo('{{true}}', { '': 'foo' }, ''); - shouldCompileTo('{{false}}', { false: 'foo' }, 'foo'); + expectTemplate('{{true}}').toCompileTo(''); + + expectTemplate('{{true}}') + .withInput({ '': 'foo' }) + .toCompileTo(''); + + expectTemplate('{{false}}') + .withInput({ false: 'foo' }) + .toCompileTo('foo'); }); it('should handle literals in subexpression', function() { - var helpers = { - foo: function(arg) { + expectTemplate('{{foo (false)}}') + .withInput({ + false: function() { + return 'bar'; + } + }) + .withHelper('foo', function(arg) { return arg; - } - }; - shouldCompileTo( - '{{foo (false)}}', - [ - { - false: function() { - return 'bar'; - } - }, - helpers - ], - 'bar' - ); + }) + .toCompileTo('bar'); }); }); diff --git a/spec/blocks.js b/spec/blocks.js index ad534aaba..f15655428 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -1,455 +1,405 @@ 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' - ); + + expectTemplate(string) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('Arrays iterate over the contents when not empty') + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage('Arrays ignore the contents when empty') + .toCompileTo('cruel 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' - ); + expectTemplate( + '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withCompileOptions({ compat: false }) + .toCompileTo('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' - ); + expectTemplate( + '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @index variable is used') + .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); 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' - ); + + expectTemplate(string) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('Arrays iterate over the contents when not empty') + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage('Arrays ignore the contents when empty') + .toCompileTo('cruel world!'); }); 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' - ); + expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ] + }) + .withMessage( + 'Templates can access variables in contexts up the stack with relative path syntax' + ) + .toCompileTo( + 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ' + ); }); 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'); + expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}') + .withInput({ + name: 'Alan', + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ] + }) + .toCompileTo('AlanAlanAlanAlanAlanAlan'); }); it('block with complex lookup using nested context', function() { - var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error); + expectTemplate( + '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}' + ).toThrow(Error); }); 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 sad OMG!'); + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: 'OMG!', + outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] + }) + .toCompileTo('Goodbye cruel sad OMG!'); }); it('works with cached blocks', function() { - var template = CompilerContext.compile( - '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', - { data: false } - ); - - var result = template({ - person: [ - { first: 'Alan', last: 'Johnson' }, - { first: 'Alan', last: 'Johnson' } - ] - }); - equals(result, 'Alan JohnsonAlan Johnson'); + expectTemplate( + '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}' + ) + .withCompileOptions({ data: false }) + .withInput({ + person: [ + { first: 'Alan', last: 'Johnson' }, + { first: 'Alan', last: 'Johnson' } + ] + }) + .toCompileTo('Alan JohnsonAlan Johnson'); }); describe('inverted sections', function() { 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." - ); + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withMessage("Inverted section rendered when value isn't set.") + .toCompileTo('Right On!'); }); 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.' - ); + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withInput({ goodbyes: false }) + .withMessage('Inverted section rendered when value is false.') + .toCompileTo('Right On!'); }); 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.' - ); + expectTemplate( + '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' + ) + .withInput({ goodbyes: [] }) + .withMessage('Inverted section rendered when value is empty set.') + .toCompileTo('Right On!'); }); it('block inverted sections', function() { - shouldCompileTo( - '{{#people}}{{name}}{{^}}{{none}}{{/people}}', - { none: 'No people' }, - 'No people' - ); + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('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' - ); + expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}') + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + + expectTemplate( + '{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}' + ) + .withInput({ none: 'No people' }) + .toCompileTo('No people'); + + expectTemplate( + '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}' + ) + .withInput({ none: 'No people' }) + .toCompileTo('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); + expectTemplate( + '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' + ).toThrow(Error); }); it('block inverted sections with empty arrays', function() { - shouldCompileTo( - '{{#people}}{{name}}{{^}}{{none}}{{/people}}', - { none: 'No people', people: [] }, - 'No people' - ); + expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') + .withInput({ + none: 'No people', + people: [] + }) + .toCompileTo('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' - ); + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); }); + it('block standalone else sections can be disabled', function() { - shouldCompileTo( - '{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', - [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }], - '\nNo people\n\n' - ); - shouldCompileTo( - '{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n', - [{ none: 'No people' }, {}, {}, { ignoreStandalone: true }], - '\nNo people\n\n' - ); + expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') + .withInput({ none: 'No people' }) + .withCompileOptions({ ignoreStandalone: true }) + .toCompileTo('\nNo people\n\n'); + + expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n') + .withInput({ none: 'No people' }) + .withCompileOptions({ ignoreStandalone: true }) + .toCompileTo('\nNo people\n\n'); }); + it('block standalone chained else sections', function() { - shouldCompileTo( - '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); - shouldCompileTo( - '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', - { none: 'No people' }, - 'No people\n' - ); + expectTemplate( + '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n' + ) + .withInput({ none: 'No people' }) + .toCompileTo('No people\n'); + + expectTemplate( + '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n' + ) + .withInput({ none: 'No people' }) + .toCompileTo('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.' - ); + expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') + .withInput({ + data: [1, 3, 5] + }) + .toCompileTo('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!' - ); + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}' + ) + .withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }) + .withCompileOptions({ compat: true }) + .toCompileTo('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!' - ); + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: { yes: 'OMG!' }, + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + }) + .withCompileOptions({ compat: true }) + .toCompileTo('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 ' - ); + expectTemplate( + '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' + ) + .withInput({ + omg: { no: 'OMG!' }, + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + }) + .withCompileOptions({ compat: true }) + .toCompileTo('Goodbye cruel '); }); }); describe('decorators', function() { it('should apply mustache decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn) { + }) + .withDecorator('decorator', function(fn) { fn.run = 'success'; return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); + it('should apply allow undefined return', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') + .withHelper('helper', function(options) { return options.fn() + options.fn.run; - } - }; - var decorators = { - decorator: function(fn) { + }) + .withDecorator('decorator', function(fn) { fn.run = 'cess'; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator}}suc{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); it('should apply block decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}' + ) + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { + }) + .withDecorator('decorator', function(fn, props, container, options) { fn.run = options.fn(); return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); + it('should support nested decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' + ) + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { - fn.run = options.fn.nested + options.fn(); - return fn; - }, - nested: function(fn, props, container, options) { - props.nested = options.fn(); - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .withDecorators({ + decorator: function(fn, props, container, options) { + fn.run = options.fn.nested + options.fn(); + return fn; + }, + nested: function(fn, props, container, options) { + props.nested = options.fn(); + } + }) + .toCompileTo('success'); }); it('should apply multiple decorators', function() { - var helpers = { - helper: function(options) { + expectTemplate( + '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' + ) + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { + }) + .withDecorator('decorator', function(fn, props, container, options) { fn.run = (fn.run || '') + options.fn(); return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}', - { hash: {}, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .toCompileTo('success'); }); it('should access parent variables', function() { - var helpers = { - helper: function(options) { + expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') + .withHelper('helper', function(options) { return options.fn.run; - } - }; - var decorators = { - decorator: function(fn, props, container, options) { + }) + .withDecorator('decorator', function(fn, props, container, options) { fn.run = options.args; return fn; - } - }; - shouldCompileTo( - '{{#helper}}{{*decorator foo}}{{/helper}}', - { hash: { foo: 'success' }, helpers: helpers, decorators: decorators }, - 'success' - ); + }) + .withInput({ foo: 'success' }) + .toCompileTo('success'); }); + it('should work with root program', function() { var run; - var decorators = { - decorator: function(fn, props, container, options) { + expectTemplate('{{*decorator "success"}}') + .withDecorator('decorator', function(fn, props, container, options) { equals(options.args[0], 'success'); run = true; return fn; - } - }; - shouldCompileTo( - '{{*decorator "success"}}', - { hash: { foo: 'success' }, decorators: decorators }, - '' - ); + }) + .withInput({ foo: 'success' }) + .toCompileTo(''); equals(run, true); }); + it('should fail when accessing variables from root', function() { var run; - var decorators = { - decorator: function(fn, props, container, options) { + expectTemplate('{{*decorator foo}}') + .withDecorator('decorator', function(fn, props, container, options) { equals(options.args[0], undefined); run = true; return fn; - } - }; - shouldCompileTo( - '{{*decorator foo}}', - { hash: { foo: 'fail' }, decorators: decorators }, - '' - ); + }) + .withInput({ foo: 'fail' }) + .toCompileTo(''); equals(run, true); }); @@ -481,6 +431,7 @@ describe('blocks', function() { equals(handlebarsEnv.decorators.foo, undefined); equals(handlebarsEnv.decorators.bar, undefined); }); + it('fails with multiple and args', function() { shouldThrow( function() { diff --git a/spec/builtins.js b/spec/builtins.js index c74092e1b..a43fb81f5 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -2,157 +2,184 @@ 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' - ); + + expectTemplate(string) + .withInput({ + goodbye: true, + world: 'world' + }) + .withMessage('if with boolean argument shows the contents when true') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: 'dummy', + world: 'world' + }) + .withMessage('if with string argument shows the contents') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: false, + world: 'world' + }) + .withMessage( + 'if with boolean argument does not show the contents when false' + ) + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ world: 'world' }) + .withMessage('if with undefined does not show the contents') + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: ['foo'], + world: 'world' + }) + .withMessage('if with non-empty array shows the contents') + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: [], + world: 'world' + }) + .withMessage('if with empty array does not show the contents') + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ + goodbye: 0, + world: 'world' + }) + .withMessage('if with zero does not show the contents') + .toCompileTo('cruel world!'); + + expectTemplate( + '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!' + ) + .withInput({ + goodbye: 0, + world: 'world' + }) + .withMessage('if with zero does not show the contents') + .toCompileTo('GOODBYE cruel world!'); }); it('if with function argument', function() { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; - shouldCompileTo( - string, - { + + expectTemplate(string) + .withInput({ goodbye: function() { return true; }, world: 'world' - }, - 'GOODBYE cruel world!', - 'if with function shows the contents when function returns true' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function shows the contents when function returns true' + ) + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return this.world; }, world: 'world' - }, - 'GOODBYE cruel world!', - 'if with function shows the contents when function returns string' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function shows the contents when function returns string' + ) + .toCompileTo('GOODBYE cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return false; }, world: 'world' - }, - 'cruel world!', - 'if with function does not show the contents when returns false' - ); - shouldCompileTo( - string, - { + }) + .withMessage( + 'if with function does not show the contents when returns false' + ) + .toCompileTo('cruel world!'); + + expectTemplate(string) + .withInput({ goodbye: function() { return this.foo; }, world: 'world' - }, - 'cruel world!', - 'if with function does not show the contents when returns undefined' - ); + }) + .withMessage( + 'if with function does not show the contents when returns undefined' + ) + .toCompileTo('cruel world!'); }); it('should not change the depth list', function() { - var string = - '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'; - shouldCompileTo( - string, - { foo: { goodbye: true }, world: 'world' }, - 'GOODBYE cruel world!' - ); + expectTemplate( + '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' + ) + .withInput({ + foo: { goodbye: true }, + world: 'world' + }) + .toCompileTo('GOODBYE cruel world!'); }); }); describe('#with', function() { it('with', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - shouldCompileTo( - string, - { person: { first: 'Alan', last: 'Johnson' } }, - 'Alan Johnson' - ); + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') + .withInput({ + person: { + first: 'Alan', + last: 'Johnson' + } + }) + .toCompileTo('Alan Johnson'); }); + it('with with function argument', function() { - var string = '{{#with person}}{{first}} {{last}}{{/with}}'; - shouldCompileTo( - string, - { + expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') + .withInput({ person: function() { - return { first: 'Alan', last: 'Johnson' }; + return { + first: 'Alan', + last: 'Johnson' + }; } - }, - 'Alan Johnson' - ); + }) + .toCompileTo('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'); + expectTemplate( + '{{#with person}}Person is present{{else}}Person is not present{{/with}}' + ).toCompileTo('Person is not present'); }); + it('with provides block parameter', function() { - var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}'; - shouldCompileTo( - string, - { person: { first: 'Alan', last: 'Johnson' } }, - 'Alan Johnson' - ); + expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') + .withInput({ + person: { + first: 'Alan', + last: 'Johnson' + } + }) + .toCompileTo('Alan Johnson'); }); - it('works when data is disabled', function() { - var template = CompilerContext.compile( - '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', - { data: false } - ); - var result = template({ person: { first: 'Alan', last: 'Johnson' } }); - equals(result, 'Alan Johnson'); + it('works when data is disabled', function() { + expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') + .withInput({ person: { first: 'Alan', last: 'Johnson' } }) + .withCompileOptions({ data: false }) + .toCompileTo('Alan Johnson'); }); }); @@ -165,55 +192,55 @@ 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' - ); + + expectTemplate(string) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage( + 'each with array argument iterates over the contents when not empty' + ) + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage('each with array argument ignores the contents when empty') + .toCompileTo('cruel 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!' - ); + expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false }) + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - hash = { goodbyes: 'cruel', world: 'world' }; - shouldCompileTo( - '{{#each .}}{{.}}{{/each}}', - [hash, , , , false], - 'cruelworld' - ); + expectTemplate('{{#each .}}{{.}}{{/each}}') + .withInput({ goodbyes: 'cruel', world: 'world' }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false }) + .toCompileTo('cruelworld'); }); it('each without context', function() { - var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - shouldCompileTo(string, [, , , ,], 'cruel !'); + expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') + .withInput(undefined) + .toCompileTo('cruel !'); }); it('each with an object and @key', function() { @@ -241,269 +268,232 @@ describe('builtin helpers', function() { true, 'each with object argument iterates over the contents when not empty' ); - shouldCompileTo(string, { goodbyes: {}, world: 'world' }, 'cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: {}, + world: 'world' + }) + .toCompileTo('cruel world!'); }); it('each with @index', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: [ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ], - world: 'world' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'The @index variable is used' - ); + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @index variable is used') + .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel 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' - ); + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @index variable is used') + .toCompileTo( + '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel 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! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' - ); + expectTemplate( + '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }], + world: 'world' + }) + .toCompileTo( + '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' + ); }); it('each object with @index', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { - a: { text: 'goodbye' }, - b: { text: 'Goodbye' }, - c: { text: 'GOODBYE' } - }, - world: 'world' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'The @index variable is used' - ); + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { + a: { text: 'goodbye' }, + b: { text: 'Goodbye' }, + c: { text: 'GOODBYE' } + }, + world: 'world' + }) + .withMessage('The @index variable is used') + .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! 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' - }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'goodbye! cruel world!', 'The @first variable is used'); + expectTemplate( + '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @first variable is used') + .toCompileTo('goodbye! cruel 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! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', - 'The @first variable is used' - ); + expectTemplate( + '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @first variable is used') + .toCompileTo( + '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel 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 @first variable is used'); + expectTemplate( + '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, + world: 'world' + }) + .withMessage('The @first variable is used') + .toCompileTo('goodbye! cruel 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! cruel world!', 'The @last variable is used'); + expectTemplate( + '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ], + world: 'world' + }) + .withMessage('The @last variable is used') + .toCompileTo('GOODBYE! cruel world!'); }); 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'); + expectTemplate( + '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, + world: 'world' + }) + .withMessage('The @last variable is used') + .toCompileTo('Goodbye! cruel world!'); }); 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 [ + expectTemplate( + '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: [ { 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' - ); + ], + world: 'world' + }) + .withMessage('The @last variable is used') + .toCompileTo( + '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!' + ); }); - it('each object when last key is an empty string', function() { - var string = - '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'; - var hash = { - goodbyes: { - a: { text: 'goodbye' }, - b: { text: 'Goodbye' }, - '': { text: 'GOODBYE' } - }, - world: 'world' - }; + it('each with function argument', function() { + var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var template = CompilerContext.compile(string); - var result = template(hash); + expectTemplate(string) + .withInput({ + goodbyes: function() { + return [ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ]; + }, + world: 'world' + }) + .withMessage( + 'each with array function argument iterates over the contents when not empty' + ) + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); - equal( - result, - '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', - 'Empty string key is not skipped' - ); + expectTemplate(string) + .withInput({ + goodbyes: [], + world: 'world' + }) + .withMessage( + 'each with array function argument ignores the contents when empty' + ) + .toCompileTo('cruel world!'); }); - it('data passed to helpers', function() { - var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'; - var hash = { letters: ['a', 'b', 'c'] }; + it('each object when last key is an empty string', function() { + expectTemplate( + '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' + ) + .withInput({ + goodbyes: { + a: { text: 'goodbye' }, + b: { text: 'Goodbye' }, + '': { text: 'GOODBYE' } + }, + world: 'world' + }) + .withMessage('Empty string key is not skipped') + .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); + }); - var template = CompilerContext.compile(string); - var result = template(hash, { - data: { - exclaim: '!' - } - }); - equal(result, 'a!b!c!', 'should output data'); + it('data passed to helpers', function() { + expectTemplate( + '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' + ) + .withInput({ letters: ['a', 'b', 'c'] }) + .withMessage('should output data') + .withRuntimeOptions({ + data: { + exclaim: '!' + } + }) + .toCompileTo('a!b!c!'); }); it('each on implicit context', function() { - shouldThrow( - function() { - var template = CompilerContext.compile( - '{{#each}}{{text}}! {{/each}}cruel world!' - ); - template({}); - }, + expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( handlebarsEnv.Exception, 'Must pass iterator to #each' ); @@ -530,25 +520,30 @@ describe('builtin helpers', function() { return new Iterator(this.arr); }; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; - var goodbyes = new Iterable([ - { text: 'goodbye' }, - { text: 'Goodbye' }, - { text: 'GOODBYE' } - ]); - var goodbyesEmpty = new Iterable([]); - var hash = { goodbyes: goodbyes, world: 'world' }; - shouldCompileTo( - string, - hash, - 'goodbye! Goodbye! GOODBYE! cruel world!', - 'each with array argument iterates over the contents when not empty' - ); - shouldCompileTo( - string, - { goodbyes: goodbyesEmpty, world: 'world' }, - 'cruel world!', - 'each with array argument ignores the contents when empty' - ); + + expectTemplate(string) + .withInput({ + goodbyes: new Iterable([ + { text: 'goodbye' }, + { text: 'Goodbye' }, + { text: 'GOODBYE' } + ]), + world: 'world' + }) + .withMessage( + 'each with array argument iterates over the contents when not empty' + ) + .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); + + expectTemplate(string) + .withInput({ + goodbyes: new Iterable([]), + world: 'world' + }) + .withMessage( + 'each with array argument ignores the contents when empty' + ) + .toCompileTo('cruel world!'); }); } }); @@ -572,36 +567,37 @@ describe('builtin helpers', function() { }); 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'); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withMessage('log should not display') + .toCompileTo(''); 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' }; + it('should call logger at data level', function() { var levelArg, logArg; handlebarsEnv.log = function(level, arg) { levelArg = level; logArg = arg; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: '03' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals('03', levelArg); equals('whee', logArg); }); + it('should output to info', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.info = function(info) { @@ -617,12 +613,13 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, hash, ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); equals(true, called); }); + it('should log at data level', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -631,13 +628,16 @@ describe('builtin helpers', function() { console.error = $error; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: '03' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); }); + it('should handle missing logger', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }, - called = false; + var called = false; console.error = undefined; console.log = function(log) { @@ -646,13 +646,15 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, [hash, , , , { level: '03' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: '03' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); }); it('should handle string log levels', function() { - var string = '{{log blah}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -660,17 +662,24 @@ describe('builtin helpers', function() { called = true; }; - shouldCompileTo(string, [hash, , , , { level: 'error' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: 'error' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); called = false; - shouldCompileTo(string, [hash, , , , { level: 'ERROR' }], ''); + expectTemplate('{{log blah}}') + .withInput({ blah: 'whee' }) + .withRuntimeOptions({ data: { level: 'ERROR' } }) + .withCompileOptions({ data: true }) + .toCompileTo(''); equals(true, called); }); + it('should handle hash log levels', function() { - var string = '{{log blah level="error"}}'; - var hash = { blah: 'whee' }; var called; console.error = function(log) { @@ -678,12 +687,13 @@ describe('builtin helpers', function() { called = true; }; - shouldCompileTo(string, hash, ''); + expectTemplate('{{log blah level="error"}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); equals(true, called); }); + it('should handle hash log levels', function() { - var string = '{{log blah level="debug"}}'; - var hash = { blah: 'whee' }; var called = false; console.info = console.log = console.error = console.debug = function() { @@ -691,12 +701,13 @@ describe('builtin helpers', function() { console.info = console.log = console.error = console.debug = $log; }; - shouldCompileTo(string, hash, ''); + expectTemplate('{{log blah level="debug"}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); equals(false, called); }); + it('should pass multiple log arguments', function() { - var string = '{{log blah "foo" 1}}'; - var hash = { blah: 'whee' }; var called; console.info = console.log = function(log1, log2, log3) { @@ -707,13 +718,13 @@ describe('builtin helpers', function() { console.log = $log; }; - shouldCompileTo(string, hash, ''); + expectTemplate('{{log blah "foo" 1}}') + .withInput({ blah: 'whee' }) + .toCompileTo(''); equals(true, called); }); it('should pass zero log arguments', function() { - var string = '{{log}}'; - var hash = { blah: 'whee' }; var called; console.info = console.log = function() { @@ -722,8 +733,8 @@ describe('builtin helpers', function() { console.log = $log; }; - expectTemplate(string) - .withInput(hash) + expectTemplate('{{log}}') + .withInput({ blah: 'whee' }) .toCompileTo(''); expect(called).to.be.true(); }); @@ -732,22 +743,15 @@ describe('builtin helpers', function() { describe('#lookup', function() { it('should lookup arbitrary content', function() { - var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}', - hash = { goodbyes: [0, 1], data: ['foo', 'bar'] }; - - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, 'foobar'); + expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}') + .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) + .toCompileTo('foobar'); }); - it('should not fail on undefined value', function() { - var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}', - hash = { goodbyes: [0, 1], data: ['foo', 'bar'] }; - var template = CompilerContext.compile(string); - var result = template(hash); - - equal(result, ''); + it('should not fail on undefined value', function() { + expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}') + .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) + .toCompileTo(''); }); }); }); diff --git a/spec/data.js b/spec/data.js index 0defdc14a..bde617326 100644 --- a/spec/data.js +++ b/spec/data.js @@ -1,31 +1,24 @@ describe('data', function() { it('passing in data to a compiled function that expects data - works with helpers', function() { - var template = CompilerContext.compile('{{hello}}', { data: true }); - - var helpers = { - hello: function(options) { + expectTemplate('{{hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.data.adjective + ' ' + this.noun; - } - }; - - var result = template( - { noun: 'cat' }, - { helpers: helpers, data: { adjective: 'happy' } } - ); - equals('happy cat', result, 'Data output by helper'); + }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withInput({ noun: 'cat' }) + .withMessage('Data output by helper') + .toCompileTo('happy cat'); }); 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'); + expectTemplate('{{@hello}}') + .withRuntimeOptions({ data: { hello: 'hello' } }) + .withMessage('@foo retrieves template data') + .toCompileTo('hello'); }); 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); helpers.let = function(options) { @@ -39,124 +32,92 @@ describe('data', function() { return options.fn(this, { data: frame }); }; - var result = template({ foo: true }, { helpers: helpers }); - equals('Hello world', result, 'Automatic data was triggered'); + expectTemplate( + '{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}' + ) + .withInput({ foo: true }) + .withHelpers(helpers) + .withMessage('Automatic data was triggered') + .toCompileTo('Hello world'); }); it('parameter data can be looked up via @foo', function() { - var template = CompilerContext.compile('{{hello @world}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world}}') + .withRuntimeOptions({ data: { world: 'world' } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: 'world' } }); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('hash values can be looked up via @foo', function() { - var template = CompilerContext.compile('{{hello noun=@world}}'); - var helpers = { - hello: function(options) { + expectTemplate('{{hello noun=@world}}') + .withRuntimeOptions({ data: { world: 'world' } }) + .withHelper('hello', function(options) { return 'Hello ' + options.hash.noun; - } - }; - - var result = template({}, { helpers: helpers, data: { world: 'world' } }); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('nested parameter data can be looked up via @foo.bar', function() { - var template = CompilerContext.compile('{{hello @world.bar}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world.bar}}') + .withRuntimeOptions({ data: { world: { bar: 'world' } } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template( - {}, - { helpers: helpers, data: { world: { bar: 'world' } } } - ); - equals( - 'Hello world', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello world'); }); it('nested parameter data does not fail with @world.bar', function() { - var template = CompilerContext.compile('{{hello @world.bar}}'); - var helpers = { - hello: function(noun) { + expectTemplate('{{hello @world.bar}}') + .withRuntimeOptions({ data: { foo: { bar: 'world' } } }) + .withHelper('hello', function(noun) { return 'Hello ' + noun; - } - }; - - var result = template( - {}, - { helpers: helpers, data: { foo: { bar: 'world' } } } - ); - equals( - 'Hello undefined', - result, - '@foo as a parameter retrieves template data' - ); + }) + .withMessage('@foo as a parameter retrieves template data') + .toCompileTo('Hello undefined'); }); it('parameter data throws when using complex scope references', function() { - var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'; - - shouldThrow(function() { - CompilerContext.compile(string); - }, Error); + expectTemplate( + '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' + ).toThrow(Error); }); it('data can be functions', function() { - var template = CompilerContext.compile('{{@hello}}'); - var result = template( - {}, - { + expectTemplate('{{@hello}}') + .withRuntimeOptions({ data: { hello: function() { return 'hello'; } } - } - ); - equals('hello', result); + }) + .toCompileTo('hello'); }); + it('data can be functions with params', function() { - var template = CompilerContext.compile('{{@hello "hello"}}'); - var result = template( - {}, - { + expectTemplate('{{@hello "hello"}}') + .withRuntimeOptions({ data: { hello: function(arg) { return arg; } } - } - ); - equals('hello', result); + }) + .toCompileTo('hello'); }); 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) { + expectTemplate( + '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}' + ) + .withInput({ bar: { baz: 'hello world' } }) + .withCompileOptions({ data: true }) + .withHelper('let', function(options) { var frame = Handlebars.createFrame(options.data); for (var prop in options.hash) { if (prop in options.hash) { @@ -164,201 +125,154 @@ describe('data', function() { } } 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'); + }) + .withRuntimeOptions({ data: {} }) + .withMessage('data variables are inherited downstream') + .toCompileTo('2hello world1'); }); 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 = { - myPartial: CompilerContext.compile('{{hello}}', { data: true }) - }; - - var helpers = { - hello: function(options) { + expectTemplate('{{>myPartial}}') + .withCompileOptions({ data: true }) + .withPartial('myPartial', '{{hello}}') + .withHelper('hello', function(options) { 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'); + }) + .withInput({ noun: 'cat' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Data output by helper inside partial') + .toCompileTo('happy cat'); }); 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) { + expectTemplate('{{hello world}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(noun, options) { 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'); + }) + .withInput({ exclaim: true, world: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Data output by helper') + .toCompileTo('happy world!'); }); 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) { + expectTemplate('{{#hello}}{{world}}{{/hello}}') + .withCompileOptions({ + data: true + }) + .withHelper('hello', function(options) { return options.fn(this); - }, - world: function(options) { + }) + .withHelper('world', function(options) { 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'); + }) + .withInput({ exclaim: true }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Data output by helper') + .toCompileTo('happy world!'); }); 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) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn({ exclaim: '?' }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { 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'); + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Data output by helper') + .toCompileTo('happy world?'); }); 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) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.data.accessData + ' ' + options.fn({ exclaim: '?' }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); - } - }; - - var result = template( - { exclaim: true, zomg: 'world' }, - { helpers: helpers, data: { adjective: 'happy', accessData: '#win' } } - ); - equals('#win happy world?', result, 'Data output by helper'); + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } }) + .withMessage('Data output by helper') + .toCompileTo('#win happy world?'); }); 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) { + expectTemplate('{{#hello}}{{world zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn( { exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } } ); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { 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'); + }) + .withInput({ exclaim: true, zomg: 'planet' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Overriden data output by helper') + .toCompileTo('sad world?'); }); 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) { + expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') + .withCompileOptions({ data: true }) + .withHelper('hello', function(options) { return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } }); - }, - world: function(thing, options) { + }) + .withHelper('world', function(thing, options) { 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'); + }) + .withInput({ exclaim: true, zomg: 'world' }) + .withRuntimeOptions({ data: { adjective: 'happy' } }) + .withMessage('Overriden data output by helper') + .toCompileTo('sad world?'); }); describe('@root', function() { it('the root context can be looked up via @root', function() { - var template = CompilerContext.compile('{{@root.foo}}'); - var result = template({ foo: 'hello' }, { data: {} }); - equals('hello', result); - - result = template({ foo: 'hello' }, {}); - equals('hello', result); + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'hello' }) + .withRuntimeOptions({ data: {} }) + .toCompileTo('hello'); + + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'hello' }) + .toCompileTo('hello'); }); + it('passed root values take priority', function() { - var template = CompilerContext.compile('{{@root.foo}}'); - var result = template({}, { data: { root: { foo: 'hello' } } }); - equals('hello', result); + expectTemplate('{{@root.foo}}') + .withInput({ foo: 'should not be used' }) + .withRuntimeOptions({ data: { root: { foo: 'hello' } } }) + .toCompileTo('hello'); }); }); describe('nesting', function() { it('the root context can be looked up via @root', function() { - var template = CompilerContext.compile( + expectTemplate( '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' - ); - var result = template( - { foo: 'hello' }, - { - helpers: { - helper: function(options) { - var frame = Handlebars.createFrame(options.data); - frame.depth = options.data.depth + 1; - return options.fn(this, { data: frame }); - } - }, + ) + .withInput({ foo: 'hello' }) + .withHelper('helper', function(options) { + var frame = Handlebars.createFrame(options.data); + frame.depth = options.data.depth + 1; + return options.fn(this, { data: frame }); + }) + .withRuntimeOptions({ data: { depth: 0 } - } - ); - equals('2 1 0', result); + }) + .toCompileTo('2 1 0'); }); }); }); diff --git a/spec/env/common.js b/spec/env/common.js index 0ecebcf45..a122f4d6e 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -129,6 +129,7 @@ function HandlebarsTestBench(templateAsString) { this.templateAsString = templateAsString; this.helpers = {}; this.partials = {}; + this.decorators = {}; this.input = {}; this.message = 'Template' + templateAsString + ' does not evaluate to expected output'; @@ -146,11 +147,43 @@ HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) { return this; }; +HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) { + var self = this; + Object.keys(helperFunctions).forEach(function(name) { + self.withHelper(name, helperFunctions[name]); + }); + return this; +}; + HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) { this.partials[name] = partialAsString; return this; }; +HandlebarsTestBench.prototype.withPartials = function(partials) { + var self = this; + Object.keys(partials).forEach(function(name) { + self.withPartial(name, partials[name]); + }); + return this; +}; + +HandlebarsTestBench.prototype.withDecorator = function( + name, + decoratorFunction +) { + this.decorators[name] = decoratorFunction; + return this; +}; + +HandlebarsTestBench.prototype.withDecorators = function(decorators) { + var self = this; + Object.keys(decorators).forEach(function(name) { + self.withDecorator(name, decorators[name]); + }); + return this; +}; + HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) { this.compileOptions = compileOptions; return this; @@ -167,19 +200,18 @@ HandlebarsTestBench.prototype.withMessage = function(message) { }; HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { - expect(this._compileAndExecute()).to.equal(expectedOutputAsString); + expect(this._compileAndExecute()).to.equal( + expectedOutputAsString, + this.message + ); }; // see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) -HandlebarsTestBench.prototype.toThrow = function( - errorLike, - errMsgMatcher, - msg -) { +HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) { var self = this; expect(function() { self._compileAndExecute(); - }).to.throw(errorLike, errMsgMatcher, msg); + }).to.throw(errorLike, errMsgMatcher, this.message); }; HandlebarsTestBench.prototype._compileAndExecute = function() { @@ -202,5 +234,6 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function() { }); combinedRuntimeOptions.helpers = this.helpers; combinedRuntimeOptions.partials = this.partials; + combinedRuntimeOptions.decorators = this.decorators; return combinedRuntimeOptions; }; diff --git a/spec/helpers.js b/spec/helpers.js index 60140a096..5166d58d6 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -1,64 +1,45 @@ describe('helpers', function() { 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) { + expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}') + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix) { return ( '' + this.text + '' ); - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye' - ); + }) + .toCompileTo('Goodbye'); }); it('helper for raw block gets raw content', function() { - var string = '{{{{raw}}}} {{test}} {{{{/raw}}}}'; - var hash = { test: 'hello' }; - var helpers = { - raw: function(options) { + expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}') + .withInput({ test: 'hello' }) + .withHelper('raw', function(options) { return options.fn(); - } - }; - shouldCompileTo( - string, - [hash, helpers], - ' {{test}} ', - 'raw block helper gets raw content' - ); + }) + .withMessage('raw block helper gets raw content') + .toCompileTo(' {{test}} '); }); 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) { + expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}') + .withInput({ test: 'hello' }) + .withHelper('raw', function(a, b, c, options) { return options.fn() + a + b + c; - } - }; - shouldCompileTo( - string, - [hash, helpers], - ' {{test}} 123', - 'raw block helper gets raw content' - ); + }) + .withMessage('raw block helper gets raw content') + .toCompileTo(' {{test}} 123'); }); describe('raw block parsing (with identity helper-function)', function() { function runWithIdentityHelper(template, expected) { - var helpers = { - identity: function(options) { + expectTemplate(template) + .withHelper('identity', function(options) { return options.fn(); - } - }; - shouldCompileTo(template, [{}, helpers], expected); + }) + .toCompileTo(expected); } it('helper for nested raw block gets raw content', function() { @@ -92,60 +73,47 @@ describe('helpers', function() { it('helper for nested raw block throw exception when with missing closing braces', function() { var string = '{{{{a}}}} {{{{/a'; - shouldThrow(function() { - Handlebars.compile(string)(); - }); + expectTemplate(string).toThrow(); }); }); it('helper block with identical context', function() { - var string = '{{#goodbyes}}{{name}}{{/goodbyes}}'; - var hash = { name: 'Alan' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}') + .withInput({ name: 'Alan' }) + .withHelper('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) + '! '; } return out; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ' - ); + }) + .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); }); + it('helper block with complex lookup expression', function() { - var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}'; - var hash = { name: 'Alan' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}') + .withInput({ name: 'Alan' }) + .withHelper('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({}) + '! '; } return out; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ' - ); + }) + .toCompileTo('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) { + expectTemplate( + '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' + ) + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix, options) { return ( 'Goodbye' - ); + }) + .toCompileTo('Goodbye'); }); 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) { + expectTemplate( + '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' + ) + .withInput({ + prefix: '/root', + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + }) + .withHelper('link', function(prefix, options) { return ( 'Goodbye' - ); + }) + .toCompileTo('Goodbye'); }); + it('helper returning undefined value', function() { - shouldCompileTo(' {{nothere}}', [{}, { nothere: function() {} }], ' '); - shouldCompileTo( - ' {{#nothere}}{{/nothere}}', - [{}, { nothere: function() {} }], - ' ' - ); + expectTemplate(' {{nothere}}') + .withHelpers({ + nothere: function() {} + }) + .toCompileTo(' '); + + expectTemplate(' {{#nothere}}{{/nothere}}') + .withHelpers({ + nothere: function() {} + }) + .toCompileTo(' '); }); 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'); + expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!') + .withInput({ world: 'world' }) + .withHelper('goodbyes', function(options) { + return options.fn({ text: 'GOODBYE' }); + }) + .withMessage('Block helper executed') + .toCompileTo('GOODBYE! cruel world!'); }); it('block helper staying in the same context', function() { - var string = '{{#form}}

{{name}}

{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { name: 'Yehuda' }, - { - helpers: { - form: function(options) { - return '
' + options.fn(this) + '
'; - } - } - } - ); - equal( - result, - '

Yehuda

', - 'Block helper executed with current context' - ); + expectTemplate('{{#form}}

{{name}}

{{/form}}') + .withInput({ name: 'Yehuda' }) + .withHelper('form', function(options) { + return '
' + options.fn(this) + '
'; + }) + .withMessage('Block helper executed with current context') + .toCompileTo('

Yehuda

'); }); it('block helper should have context in this', function() { - var source = - '
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
'; function link(options) { return '' + options.fn(this) + ''; } - var data = { - people: [ - { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 } - ] - }; - shouldCompileTo( - source, - [data, { link: link }], - '' - ); + expectTemplate( + '
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
' + ) + .withInput({ + people: [ + { name: 'Alan', id: 1 }, + { name: 'Yehuda', id: 2 } + ] + }) + .withHelper('link', link) + .toCompileTo( + '' + ); }); it('block helper for undefined value', function() { - shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, ''); + expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo(''); }); it('block helper passing a new context', function() { - var string = '{{#form yehuda}}

{{name}}

{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { yehuda: { name: 'Yehuda' } }, - { - helpers: { - form: function(context, options) { - return '
' + options.fn(context) + '
'; - } - } - } - ); - equal(result, '

Yehuda

', 'Context variable resolved'); + expectTemplate('{{#form yehuda}}

{{name}}

{{/form}}') + .withInput({ yehuda: { name: 'Yehuda' } }) + .withHelper('form', function(context, options) { + return '
' + options.fn(context) + '
'; + }) + .withMessage('Context variable resolved') + .toCompileTo('

Yehuda

'); }); it('block helper passing a complex path context', function() { - var string = '{{#form yehuda/cat}}

{{name}}

{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }, - { - helpers: { - form: function(context, options) { - return '
' + options.fn(context) + '
'; - } - } - } - ); - equal( - result, - '

Harold

', - 'Complex path variable resolved' - ); + expectTemplate('{{#form yehuda/cat}}

{{name}}

{{/form}}') + .withInput({ yehuda: { name: 'Yehuda', cat: { name: 'Harold' } } }) + .withHelper('form', function(context, options) { + return '
' + options.fn(context) + '
'; + }) + .withMessage('Complex path variable resolved') + .toCompileTo('

Harold

'); }); it('nested block helpers', function() { - var string = - '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}'; - var template = CompilerContext.compile(string); - - var result = template( - { + expectTemplate( + '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}' + ) + .withInput({ yehuda: { name: 'Yehuda' } - }, - { - helpers: { - link: function(options) { - return '' + options.fn(this) + ''; - }, - form: function(context, options) { - return '
' + options.fn(context) + '
'; - } - } - } - ); - equal( - result, - '

Yehuda

Hello
', - 'Both blocks executed' - ); + }) + .withHelper('link', function(options) { + return '' + options.fn(this) + ''; + }) + .withHelper('form', function(context, options) { + return '
' + options.fn(context) + '
'; + }) + .withMessage('Both blocks executed') + .toCompileTo('

Yehuda

Hello
'); }); it('block helper inverted sections', function() { @@ -345,35 +261,28 @@ describe('helpers', function() { } } - var hash = { people: [{ name: 'Alan' }, { name: 'Yehuda' }] }; - var empty = { people: [] }; - var rootMessage = { - people: [], - message: "Nobody's here" - }; - - 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 }], - '
  • Alan
  • Yehuda
', - 'an inverse wrapper is passed in as a new context' - ); - shouldCompileTo( - string, - [empty, { list: list }], - "

Nobody's here

", - 'an inverse wrapper can be optionally called' - ); - shouldCompileTo( - messageString, - [rootMessage, { list: list }], - '

Nobody's here

', - 'the context of an inverse is the parent of the block' - ); + expectTemplate(string) + .withInput({ people: [{ name: 'Alan' }, { name: 'Yehuda' }] }) + .withHelpers({ list: list }) + .withMessage('an inverse wrapper is passed in as a new context') + .toCompileTo('
  • Alan
  • Yehuda
'); + + expectTemplate(string) + .withInput({ people: [] }) + .withHelpers({ list: list }) + .withMessage('an inverse wrapper can be optionally called') + .toCompileTo("

Nobody's here

"); + + expectTemplate('{{#list people}}Hello{{^}}{{message}}{{/list}}') + .withInput({ + people: [], + message: "Nobody's here" + }) + .withHelpers({ list: list }) + .withMessage('the context of an inverse is the parent of the block') + .toCompileTo('

Nobody's here

'); }); it('pathed lambas with parameters', function() { @@ -388,84 +297,73 @@ describe('helpers', function() { return 'fail'; } }; - shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning'); - shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning'); + + expectTemplate('{{./helper 1}}') + .withInput(hash) + .withHelpers(helpers) + .toCompileTo('winning'); + + expectTemplate('{{hash/helper 1}}') + .withInput(hash) + .withHelpers(helpers) + .toCompileTo('winning'); }); describe('helpers hash', function() { it('providing a helpers hash', function() { - shouldCompileTo( - 'Goodbye {{cruel}} {{world}}!', - [ - { cruel: 'cruel' }, - { - world: function() { - return 'world'; - } + expectTemplate('Goodbye {{cruel}} {{world}}!') + .withInput({ cruel: 'cruel' }) + .withHelpers({ + world: function() { + return 'world'; } - ], - 'Goodbye cruel world!', - 'helpers hash is available' - ); - - shouldCompileTo( - 'Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!', - [ - { iter: [{ cruel: 'cruel' }] }, - { - world: function() { - return 'world'; - } + }) + .withMessage('helpers hash is available') + .toCompileTo('Goodbye cruel world!'); + + expectTemplate('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!') + .withInput({ iter: [{ cruel: 'cruel' }] }) + .withHelpers({ + world: function() { + return 'world'; } - ], - 'Goodbye cruel world!', - 'helpers hash is available inside other blocks' - ); + }) + .withMessage('helpers hash is available inside other blocks') + .toCompileTo('Goodbye cruel world!'); }); it('in cases of conflict, helpers win', function() { - shouldCompileTo( - '{{{lookup}}}', - [ - { lookup: 'Explicit' }, - { - lookup: function() { - return 'helpers'; - } + expectTemplate('{{{lookup}}}') + .withInput({ lookup: 'Explicit' }) + .withHelpers({ + lookup: function() { + return 'helpers'; } - ], - 'helpers', - 'helpers hash has precedence escaped expansion' - ); - shouldCompileTo( - '{{lookup}}', - [ - { lookup: 'Explicit' }, - { - lookup: function() { - return 'helpers'; - } + }) + .withMessage('helpers hash has precedence escaped expansion') + .toCompileTo('helpers'); + + expectTemplate('{{lookup}}') + .withInput({ lookup: 'Explicit' }) + .withHelpers({ + lookup: function() { + return 'helpers'; } - ], - 'helpers', - 'helpers hash has precedence simple expansion' - ); + }) + .withMessage('helpers hash has precedence simple expansion') + .toCompileTo('helpers'); }); it('the helpers hash is available is nested contexts', function() { - shouldCompileTo( - '{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}', - [ - { outer: { inner: { unused: [] } } }, - { - helper: function() { - return 'helper'; - } + expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}') + .withInput({ outer: { inner: { unused: [] } } }) + .withHelpers({ + helper: function() { + return 'helper'; } - ], - 'helper', - 'helpers hash is available in nested contexts.' - ); + }) + .withMessage('helpers hash is available in nested contexts.') + .toCompileTo('helper'); }); it('the helper hash should augment the global hash', function() { @@ -473,18 +371,16 @@ describe('helpers', function() { return 'found it!'; }); - shouldCompileTo( - '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}', - [ - { cruel: 'cruel' }, - { - world: function() { - return 'world!'; - } + expectTemplate( + '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' + ) + .withInput({ cruel: 'cruel' }) + .withHelpers({ + world: function() { + return 'world!'; } - ], - 'found it! Goodbye cruel world!!' - ); + }) + .toCompileTo('found it! Goodbye cruel world!!'); }); }); @@ -513,12 +409,13 @@ describe('helpers', function() { } }); - shouldCompileTo( - '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}', - [{ cruel: 'cruel' }], - 'found it! Goodbye cruel world!!' - ); + expectTemplate( + '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' + ) + .withInput({ cruel: 'cruel' }) + .toCompileTo('found it! Goodbye cruel world!!'); }); + it('fails with multiple and args', function() { shouldThrow( function() { @@ -541,9 +438,8 @@ describe('helpers', function() { }); it('decimal number literals work', function() { - var string = 'Message: {{hello -1.2 1.2}}'; - var helpers = { - hello: function(times, times2) { + expectTemplate('Message: {{hello -1.2 1.2}}') + .withHelper('hello', function(times, times2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -551,39 +447,27 @@ describe('helpers', function() { times2 = 'NaN'; } return 'Hello ' + times + ' ' + times2 + ' times'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -1.2 1.2 times', - 'template with a negative integer literal' - ); + }) + .withMessage('template with a negative integer literal') + .toCompileTo('Message: Hello -1.2 1.2 times'); }); it('negative number literals work', function() { - var string = 'Message: {{hello -12}}'; - var helpers = { - hello: function(times) { + expectTemplate('Message: {{hello -12}}') + .withHelper('hello', function(times) { if (typeof times !== 'number') { times = 'NaN'; } return 'Hello ' + times + ' times'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -12 times', - 'template with a negative integer literal' - ); + }) + .withMessage('template with a negative integer literal') + .toCompileTo('Message: Hello -12 times'); }); 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) { + expectTemplate('Message: {{hello "world" 12 true false}}') + .withHelper('hello', function(param, times, bool1, bool2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -596,115 +480,74 @@ describe('helpers', function() { return ( 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 ); - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello world 12 times: true false', - 'template with a simple String literal' - ); + }) + .withMessage('template with a simple String literal') + .toCompileTo('Message: Hello world 12 times: true false'); }); 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); + expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error); }); it('escaping a String is possible', function() { - var string = 'Message: {{{hello "\\"world\\""}}}'; - var helpers = { - hello: function(param) { + expectTemplate('Message: {{{hello "\\"world\\""}}}') + .withHelper('hello', function(param) { return 'Hello ' + param; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello "world"', - 'template with an escaped String literal' - ); + }) + .withMessage('template with an escaped String literal') + .toCompileTo('Message: Hello "world"'); }); it("it works with ' marks", function() { - var string = 'Message: {{{hello "Alan\'s world"}}}'; - var helpers = { - hello: function(param) { + expectTemplate('Message: {{{hello "Alan\'s world"}}}') + .withHelper('hello', function(param) { return 'Hello ' + param; - } - }; - shouldCompileTo( - string, - [{}, helpers], - "Message: Hello Alan's world", - "template with a ' mark" - ); + }) + .withMessage("template with a ' mark") + .toCompileTo("Message: Hello Alan's world"); }); }); it('negative number literals work', function() { - var string = 'Message: {{hello -12}}'; - var helpers = { - hello: function(times) { + expectTemplate('Message: {{hello -12}}') + .withHelper('hello', function(times) { if (typeof times !== 'number') { times = 'NaN'; } return 'Hello ' + times + ' times'; - } - }; - shouldCompileTo( - string, - [{}, helpers], - 'Message: Hello -12 times', - 'template with a negative integer literal' - ); + }) + .withMessage('template with a negative integer literal') + .toCompileTo('Message: Hello -12 times'); }); 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) { + expectTemplate('Message: {{goodbye cruel world}}') + .withInput({ cruel: 'cruel', world: 'world' }) + .withHelper('goodbye', function(cruel, world) { return 'Goodbye ' + cruel + ' ' + world; - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Message: Goodbye cruel world', - 'regular helpers with multiple params' - ); + }) + .withMessage('regular helpers with multiple params') + .toCompileTo('Message: Goodbye cruel 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) { + expectTemplate( + 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}' + ) + .withInput({ cruel: 'cruel', world: 'world' }) + .withHelper('goodbye', function(cruel, world, options) { return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world }); - } - }; - shouldCompileTo( - string, - [hash, helpers], - 'Message: Goodbye cruel world', - 'block helpers with multiple params' - ); + }) + .withMessage('block helpers with multiple params') + .toCompileTo('Message: Goodbye cruel world'); }); }); describe('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) { + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -714,50 +557,36 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var context = {}; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL WORLD 12 TIMES', 'Helper output hash'); + }) + .withMessage('Helper output hash') + .toCompileTo('GOODBYE CRUEL WORLD 12 TIMES'); }); 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; - } else if (options.hash.print === false) { - return 'NOT PRINTING'; - } else { - return 'THIS SHOULD NOT HAPPEN'; - } + function goodbye(options) { + if (options.hash.print === true) { + return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world; + } else if (options.hash.print === false) { + return 'NOT PRINTING'; + } else { + return 'THIS SHOULD NOT HAPPEN'; } - }; - - var context = {}; + } - 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'); + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=true}}') + .withHelper('goodbye', goodbye) + .withMessage('Helper output hash') + .toCompileTo('GOODBYE CRUEL WORLD'); - template = CompilerContext.compile( - '{{goodbye cruel="CRUEL" world="WORLD" print=false}}' - ); - result = template(context, { helpers: helpers }); - equals(result, 'NOT PRINTING', 'Boolean helper parameter honored'); + expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" print=false}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean helper parameter honored') + .toCompileTo('NOT PRINTING'); }); 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) { + expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -767,20 +596,14 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output'); + }) + .withMessage('Hash parameters output') + .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); 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) { + expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') + .withHelper('goodbye', function(options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -790,200 +613,173 @@ describe('helpers', function() { options.hash.times + ' TIMES' ); - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output'); + }) + .withMessage('Hash parameters output') + .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); 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); - } else if (options.hash.print === false) { - return 'NOT PRINTING'; - } else { - return 'THIS SHOULD NOT HAPPEN'; - } + function goodbye(options) { + if (options.hash.print === true) { + return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this); + } else if (options.hash.print === false) { + return 'NOT PRINTING'; + } else { + 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'); + expectTemplate('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean hash parameter honored') + .toCompileTo('GOODBYE CRUEL world'); - template = CompilerContext.compile( - '{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}' - ); - result = template({}, { helpers: helpers }); - equals(result, 'NOT PRINTING', 'Boolean hash parameter honored'); + expectTemplate('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}') + .withHelper('goodbye', goodbye) + .withMessage('Boolean hash parameter honored') + .toCompileTo('NOT PRINTING'); }); }); describe('helperMissing', function() { it('if a context is not found, helperMissing is used', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{hello}} {{link_to world}}'); - template({}); - }, - undefined, + expectTemplate('{{hello}} {{link_to world}}').toThrow( /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' }; - - var helpers = { - helperMissing: function(mesg, options) { + expectTemplate('{{hello}} {{link_to world}}') + .withInput({ hello: 'Hello', world: 'world' }) + .withHelper('helperMissing', function(mesg, options) { if (options.name === 'link_to') { return new Handlebars.SafeString('' + mesg + ''); } - } - }; - - shouldCompileTo(string, [context, helpers], 'Hello world'); + }) + .toCompileTo('Hello world'); }); 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) { + expectTemplate('{{hello}} {{link_to}}') + .withInput({ hello: 'Hello', world: 'world' }) + .withHelper('helperMissing', function(options) { if (options.name === 'link_to') { return new Handlebars.SafeString('winning'); } - } - }; - - shouldCompileTo(string, [context, helpers], 'Hello winning'); + }) + .toCompileTo('Hello winning'); }); }); 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); + expectTemplate('{{hello}}') + .withCompileOptions({ + knownHelpers: { hello: true } + }) + .withHelper('hello', function() { + return 'foo'; + }) + .toCompileTo('foo'); }); 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); + expectTemplate('{{typeof hello}}') + .withCompileOptions({ + knownHelpers: { typeof: true }, + knownHelpersOnly: true + }) + .withHelper('typeof', function(arg) { + return typeof arg; + }) + .withHelper('hello', function() { + return 'foo'; + }) + .toCompileTo('undefined'); }); - 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); + it('Builtin helpers available in knownHelpers only mode', function() { + expectTemplate('{{#unless foo}}bar{{/unless}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .toCompileTo('bar'); }); - 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); + it('Field lookup works in knownHelpers only mode', function() { + expectTemplate('{{foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); }); - 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); + it('Conditional blocks work in knownHelpers only mode', function() { + expectTemplate('{{#foo}}bar{{/foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: 'baz' }) + .toCompileTo('bar'); }); - 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); + it('Invert blocks work in knownHelpers only mode', function() { + expectTemplate('{{^foo}}bar{{/foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ foo: false }) + .toCompileTo('bar'); }); + 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); + expectTemplate('{{foo}}') + .withCompileOptions({ + knownHelpersOnly: true + }) + .withInput({ + foo: function() { + return this.bar; + }, + bar: 'bar' + }) + .toCompileTo('bar'); }); + it('Unknown helper call in knownHelpers only mode should throw', function() { - shouldThrow(function() { - CompilerContext.compile('{{typeof hello}}', { knownHelpersOnly: true }); - }, Error); + expectTemplate('{{typeof hello}}') + .withCompileOptions({ knownHelpersOnly: true }) + .toThrow(Error); }); }); 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'); + expectTemplate('{{#truthy}}yep{{/truthy}}') + .withInput({ + truthy: function() { + return true; + } + }) + .toCompileTo('yep'); }); + 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, ''); + expectTemplate('{{#truthy}}yep{{/truthy}}') + .withInput({ + truthy: function() { + return this.truthiness(); + }, + truthiness: function() { + return false; + } + }) + .toCompileTo(''); }); }); describe('name field', function() { - var context = {}; var helpers = { blockHelperMissing: function() { return 'missing: ' + arguments[arguments.length - 1].name; @@ -997,212 +793,173 @@ describe('helpers', function() { }; it('should include in ambiguous mustache calls', function() { - shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper'); + expectTemplate('{{helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in helper mustache calls', function() { - shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper'); + expectTemplate('{{helper 1}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in ambiguous block calls', function() { - shouldCompileTo( - '{{#helper}}{{/helper}}', - [context, helpers], - 'ran: helper' - ); + expectTemplate('{{#helper}}{{/helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); + it('should include in simple block calls', function() { - shouldCompileTo( - '{{#./helper}}{{/./helper}}', - [context, helpers], - 'missing: ./helper' - ); + expectTemplate('{{#./helper}}{{/./helper}}') + .withHelpers(helpers) + .toCompileTo('missing: ./helper'); }); + it('should include in helper block calls', function() { - shouldCompileTo( - '{{#helper 1}}{{/helper}}', - [context, helpers], - 'ran: helper' - ); + expectTemplate('{{#helper 1}}{{/helper}}') + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); - it('should include in known helper calls', function() { - var template = CompilerContext.compile('{{helper}}', { - knownHelpers: { helper: true }, - knownHelpersOnly: true - }); - equal(template({}, { helpers: helpers }), 'ran: helper'); + it('should include in known helper calls', function() { + expectTemplate('{{helper}}') + .withCompileOptions({ + knownHelpers: { helper: true }, + knownHelpersOnly: true + }) + .withHelpers(helpers) + .toCompileTo('ran: helper'); }); it('should include full id', function() { - shouldCompileTo( - '{{#foo.helper}}{{/foo.helper}}', - [{ foo: {} }, helpers], - 'missing: foo.helper' - ); + expectTemplate('{{#foo.helper}}{{/foo.helper}}') + .withInput({ foo: {} }) + .withHelpers(helpers) + .toCompileTo('missing: foo.helper'); }); it('should include full id if a hash is passed', function() { - shouldCompileTo( - '{{#foo.helper bar=baz}}{{/foo.helper}}', - [{ foo: {} }, helpers], - 'helper missing: foo.helper' - ); + expectTemplate('{{#foo.helper bar=baz}}{{/foo.helper}}') + .withInput({ foo: {} }) + .withHelpers(helpers) + .toCompileTo('helper missing: foo.helper'); }); }); describe('name conflicts', function() { it('helpers take precedence over same-named context properties', function() { - var template = CompilerContext.compile('{{goodbye}} {{cruel world}}'); - - var helpers = { - goodbye: function() { + expectTemplate('{{goodbye}} {{cruel world}}') + .withHelper('goodbye', function() { return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD'); }); it('helpers take precedence over same-named context properties$', function() { - var template = CompilerContext.compile( - '{{#goodbye}} {{cruel world}}{{/goodbye}}' - ); - - var helpers = { - goodbye: function(options) { + expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}') + .withHelper('goodbye', function(options) { return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD'); }); it('Scoped names take precedence over helpers', function() { - var template = CompilerContext.compile( - '{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}' - ); - - var helpers = { - goodbye: function() { + expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}') + .withHelper('goodbye', function() { return this.goodbye.toUpperCase(); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - var result = template(context, { helpers: helpers }); - equals( - result, - 'goodbye cruel WORLD cruel GOODBYE', - 'Helper not executed' - ); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper not executed') + .toCompileTo('goodbye cruel WORLD cruel GOODBYE'); }); it('Scoped names take precedence over block helpers', function() { - var template = CompilerContext.compile( + expectTemplate( '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' - ); - - var helpers = { - goodbye: function(options) { + ) + .withHelper('goodbye', function(options) { return this.goodbye.toUpperCase() + options.fn(this); - }, - - cruel: function(world) { + }) + .withHelper('cruel', function(world) { return 'cruel ' + world.toUpperCase(); - } - }; - - var context = { - goodbye: 'goodbye', - world: 'world' - }; - - var result = template(context, { helpers: helpers }); - equals(result, 'GOODBYE cruel WORLD goodbye', 'Helper executed'); + }) + .withInput({ + goodbye: 'goodbye', + world: 'world' + }) + .withMessage('Helper executed') + .toCompileTo('GOODBYE cruel WORLD goodbye'); }); }); describe('block params', function() { it('should take presedence over context values', function() { - var hash = { value: 'foo' }; - var helpers = { - goodbyes: function(options) { + expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') + .withInput({ value: 'foo' }) + .withHelper('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' - ); + }) + .toCompileTo('1foo'); }); + it('should take presedence over helper values', function() { - var hash = {}; - var helpers = { - value: function() { + expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') + .withHelper('value', function() { return 'foo'; - }, - goodbyes: function(options) { + }) + .withHelper('goodbyes', function(options) { equals(options.fn.blockParams, 1); return options.fn({}, { blockParams: [1, 2] }); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}', - [hash, helpers], - '1foo' - ); + }) + .toCompileTo('1foo'); }); + it('should not take presedence over pathed values', function() { - var hash = { value: 'bar' }; - var helpers = { - value: function() { + expectTemplate( + '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' + ) + .withInput({ value: 'bar' }) + .withHelper('value', function() { return 'foo'; - }, - goodbyes: function(options) { + }) + .withHelper('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' - ); + }) + .toCompileTo('barfoo'); }); + it('should take presednece over parent block params', function() { - var hash = { value: 'foo' }, - value = 1; - var helpers = { - goodbyes: function(options) { + var value = 1; + expectTemplate( + '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' + ) + .withInput({ value: 'foo' }) + .withHelper('goodbyes', function(options) { return options.fn( { value: 'bar' }, { @@ -1210,120 +967,68 @@ describe('helpers', function() { options.fn.blockParams === 1 ? [value++, value++] : undefined } ); - } - }; - shouldCompileTo( - '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', - [hash, helpers], - '13foo' - ); + }) + .toCompileTo('13foo'); }); it('should allow block params on chained helpers', function() { - var hash = { value: 'foo' }; - var helpers = { - goodbyes: function(options) { + expectTemplate( + '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' + ) + .withInput({ value: 'foo' }) + .withHelper('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' - ); + }) + .toCompileTo('1foo'); }); }); describe('built-in helpers malformed arguments ', function() { it('if helper - too few arguments', function() { - var template = CompilerContext.compile('{{#if}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, string', function() { - var template = CompilerContext.compile('{{#if test "string"}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test "string"}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, undefined', function() { - var template = CompilerContext.compile('{{#if test undefined}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test undefined}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('if helper - too many arguments, null', function() { - var template = CompilerContext.compile('{{#if test null}}{{/if}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#if test null}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); it('unless helper - too few arguments', function() { - var template = CompilerContext.compile('{{#unless}}{{/unless}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#unless}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); it('unless helper - too many arguments', function() { - var template = CompilerContext.compile( - '{{#unless test null}}{{/unless}}' - ); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#unless test null}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); it('with helper - too few arguments', function() { - var template = CompilerContext.compile('{{#with}}{{/with}}'); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#with}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); it('with helper - too many arguments', function() { - var template = CompilerContext.compile( - '{{#with test "string"}}{{/with}}' - ); - shouldThrow( - function() { - template({}); - }, - undefined, + expectTemplate('{{#with test "string"}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index e97cbb083..ed2dc8c56 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -20,14 +20,18 @@ describe('javascript-compiler api', function() { return parent + '.bar_' + name; }; /* eslint-disable camelcase */ - shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food'); + expectTemplate('{{foo}}') + .withInput({ bar_foo: 'food' }) + .toCompileTo('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'); + expectTemplate('{{foo}} {{~null~}}') + .withInput({ foo: 'food' }) + .toCompileTo('food'); }); }); describe('#compilerInfo', function() { @@ -49,7 +53,9 @@ describe('javascript-compiler api', function() { throw new Error("It didn't work"); } }; - shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food '); + expectTemplate('{{foo}} ') + .withInput({ foo: 'food' }) + .toCompileTo('food '); }); }); describe('buffer', function() { @@ -70,7 +76,9 @@ describe('javascript-compiler api', function() { handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() { return this.quotedString('foo_'); }; - shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food '); + expectTemplate('{{foo}} ') + .withInput({ foo: 'food' }) + .toCompileTo('foo_food '); }); it('should allow append buffer override', function() { handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function( @@ -78,7 +86,9 @@ describe('javascript-compiler api', function() { ) { return $superAppend.call(this, [string, ' + "_foo"']); }; - shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo'); + expectTemplate('{{foo}}') + .withInput({ foo: 'food' }) + .toCompileTo('food_foo'); }); }); diff --git a/spec/partials.js b/spec/partials.js index d00d4147e..df092267d 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -8,18 +8,18 @@ describe('partials', function() { { 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) ' - ); + + expectTemplate(string) + .withInput(hash) + .withPartials({ dude: partial }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + + expectTemplate(string) + .withInput(hash) + .withPartials({ dude: partial }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); it('dynamic partials', function() { @@ -36,63 +36,48 @@ describe('partials', 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) ' - ); + + expectTemplate(string) + .withInput(hash) + .withHelpers(helpers) + .withPartials({ dude: partial }) + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); + + expectTemplate(string) + .withInput(hash) + .withHelpers(helpers) + .withPartials({ dude: partial }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false }) + .toCompileTo('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() { + expectTemplate('Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withHelper('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' - ); + }) + .withPartial('dude', '{{name}} ({{url}}) ') + .toThrow(Handlebars.Exception, 'The partial missing could not be found'); }); it('partials with context', function() { - var string = 'Dudes: {{>dude dudes}}'; - var partial = '{{#this}}{{name}} ({{url}}) {{/this}}'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ', - 'Partials can be passed a context' - ); + expectTemplate('Dudes: {{>dude dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{#this}}{{name}} ({{url}}) {{/this}}') + .withMessage('Partials can be passed a context') + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); it('partials with no context', function() { @@ -103,98 +88,73 @@ describe('partials', function() { { name: 'Alan', url: 'http://alan' } ] }; - shouldCompileToWithPartials( - 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}', - [hash, {}, { dude: partial }, { explicitPartialContext: true }], - true, - 'Dudes: () () ' - ); - shouldCompileToWithPartials( - 'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}', - [hash, {}, { dude: partial }, { explicitPartialContext: true }], - true, - 'Dudes: foo () foo () ' - ); + + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') + .withInput(hash) + .withPartial('dude', partial) + .withCompileOptions({ explicitPartialContext: true }) + .toCompileTo('Dudes: () () '); + + expectTemplate('Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}') + .withInput(hash) + .withPartial('dude', partial) + .withCompileOptions({ explicitPartialContext: true }) + .toCompileTo('Dudes: foo () foo () '); }); it('partials with string context', function() { - var string = 'Dudes: {{>dude "dudes"}}'; - var partial = '{{.}}'; - var hash = {}; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: dudes' - ); + expectTemplate('Dudes: {{>dude "dudes"}}') + .withPartial('dude', '{{.}}') + .toCompileTo('Dudes: dudes'); }); it('partials with undefined context', function() { - var string = 'Dudes: {{>dude dudes}}'; - var partial = '{{foo}} Empty'; - var hash = {}; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: Empty' - ); + expectTemplate('Dudes: {{>dude dudes}}') + .withPartial('dude', '{{foo}} Empty') + .toCompileTo('Dudes: Empty'); }); it('partials with duplicate parameters', function() { - shouldThrow( - function() { - CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}'); - }, + expectTemplate('Dudes: {{>dude dudes foo bar=baz}}').toThrow( Error, 'Unsupported number of partial arguments: 2 - 1:7' ); }); it('partials with parameters', function() { - var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}'; - var partial = '{{others.foo}}{{name}} ({{url}}) '; - var hash = { - foo: 'bar', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }], - true, - 'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ', - 'Basic partials output based on current context.' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}') + .withInput({ + foo: 'bar', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{others.foo}}{{name}} ({{url}}) ') + .withMessage('Basic partials output based on current context.') + .toCompileTo('Dudes: barYehuda (http://yehuda) barAlan (http://alan) '); }); it('partial in a partial', function() { - var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}'; - var dude = '{{name}} {{> url}} '; - var url = '{{url}}'; - var hash = { - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: dude, url: url }], - true, - 'Dudes: Yehuda http://yehuda Alan http://alan ', - 'Partials are rendered inside of other partials' - ); + expectTemplate('Dudes: {{#dudes}}{{>dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}} {{> url}} ', + url: '{{url}}' + }) + .withMessage('Partials are rendered inside of other partials') + .toCompileTo( + 'Dudes: Yehuda http://yehuda Alan http://alan ' + ); }); it('rendering undefined partial throws an exception', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{> whatever}}'); - template(); - }, + expectTemplate('{{> whatever}}').toThrow( Handlebars.Exception, 'The partial whatever could not be found' ); @@ -212,87 +172,60 @@ describe('partials', function() { }); it('rendering template partial in vm mode throws an exception', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('{{> whatever}}'); - template(); - }, + expectTemplate('{{> whatever}}').toThrow( 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.' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', partial) + .withMessage('Function partials output based in VM.') + .toCompileTo('Dudes: Yehuda (http://yehuda) Alan (http://alan) '); }); 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' - ); + expectTemplate('Dudes: {{>dude}} {{anotherDude}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('dude', '{{name}}') + .withMessage('Regular selectors can follow a partial') + .toCompileTo('Dudes: Jeepers Creepers'); }); 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' - ); + expectTemplate('Dudes: {{> shared/dude}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude', '{{name}}') + .withMessage('Partials can use literal paths') + .toCompileTo('Dudes: Jeepers'); }); 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' - ); + expectTemplate('Dudes: {{> shared/dude.thing}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude.thing', '{{name}}') + .withMessage('Partials can use literal with points in paths') + .toCompileTo('Dudes: Jeepers'); }); it('Global Partials', function() { handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}'); - 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' - ); + expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('shared/dude', '{{name}}') + .withMessage('Partials can use globals or passed') + .toCompileTo('Dudes: Jeepers Creepers'); handlebarsEnv.unregisterPartial('globalTest'); equals(handlebarsEnv.partials.globalTest, undefined); @@ -304,408 +237,317 @@ describe('partials', function() { globalTest: '{{anotherDude}}' }); - 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' - ); + expectTemplate('Dudes: {{> shared/dude}} {{> globalTest}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('notused', 'notused') // trick the test bench into running with partials enabled + .withMessage('Partials can use globals or passed') + .toCompileTo('Dudes: Jeepers Creepers'); }); 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' - ); + expectTemplate('Dudes: {{> 404}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial(404, '{{name}}') + .withMessage('Partials can use literal paths') + .toCompileTo('Dudes: Jeepers'); }); 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' - ); + expectTemplate('Dudes: {{> 404/asdf?.bar}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('404/asdf?.bar', '{{name}}') + .withMessage('Partials can use literal paths') + .toCompileTo('Dudes: Jeepers'); }); 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' - ); + expectTemplate('Dudes: {{> [+404/asdf?.bar]}}') + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('+404/asdf?.bar', '{{name}}') + .withMessage('Partials can use literal paths') + .toCompileTo('Dudes: Jeepers'); }); 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' - ); + expectTemplate("Dudes: {{> '+404/asdf?.bar'}}") + .withInput({ name: 'Jeepers', anotherDude: 'Creepers' }) + .withPartial('+404/asdf?.bar', '{{name}}') + .withMessage('Partials can use literal paths') + .toCompileTo('Dudes: Jeepers'); }); 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: ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '') + .toCompileTo('Dudes: '); }); it('throw on missing partial', function() { var compile = handlebarsEnv.compile; + var compileWithPartial = CompilerContext.compileWithPartial; handlebarsEnv.compile = undefined; - shouldThrow( - function() { - shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], ''); - }, - Error, - /The partial dude could not be compiled/ - ); + CompilerContext.compileWithPartial = CompilerContext.compile; + expectTemplate('{{> dude}}') + .withPartials({ dude: 'fail' }) + .toThrow(Error, /The partial dude could not be compiled/); handlebarsEnv.compile = compile; + CompilerContext.compileWithPartial = compileWithPartial; }); describe('partial blocks', function() { it('should render partial block as default', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, {}], - true, - 'success' - ); + expectTemplate('{{#> dude}}success{{/dude}}').toCompileTo('success'); }); + it('should execute default block with proper context', function() { - shouldCompileToWithPartials( - '{{#> dude context}}{{value}}{{/dude}}', - [{ context: { value: 'success' } }, {}, {}], - true, - 'success' - ); + expectTemplate('{{#> dude context}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .toCompileTo('success'); }); + it('should propagate block parameters to default block', function() { - shouldCompileToWithPartials( - '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', - [{ context: { value: 'success' } }, {}, {}], - true, - 'success' - ); + expectTemplate( + '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' + ) + .withInput({ context: { value: 'success' } }) + .toCompileTo('success'); }); it('should not use partial block if partial exists', function() { - shouldCompileToWithPartials( - '{{#> dude}}fail{{/dude}}', - [{}, {}, { dude: 'success' }], - true, - 'success' - ); + expectTemplate('{{#> dude}}fail{{/dude}}') + .withPartials({ dude: 'success' }) + .toCompileTo('success'); }); it('should render block from partial', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, { dude: '{{> @partial-block }}' }], - true, - 'success' - ); + expectTemplate('{{#> dude}}success{{/dude}}') + .withPartials({ dude: '{{> @partial-block }}' }) + .toCompileTo('success'); }); + it('should be able to render the partial-block twice', function() { - shouldCompileToWithPartials( - '{{#> dude}}success{{/dude}}', - [{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }], - true, - 'success success' - ); + expectTemplate('{{#> dude}}success{{/dude}}') + .withPartials({ dude: '{{> @partial-block }} {{> @partial-block }}' }) + .toCompileTo('success success'); }); + it('should render block from partial with context', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{#with context}}{{> @partial-block }}{{/with}}' } - ], - true, - 'success' - ); + expectTemplate('{{#> dude}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: '{{#with context}}{{> @partial-block }}{{/with}}' + }) + .toCompileTo('success'); }); it('should be able to access the @data frame from a partial-block', function() { - shouldCompileToWithPartials( - '{{#> dude}}in-block: {{@root/value}}{{/dude}}', - [ - { value: 'success' }, - {}, - { - dude: - 'before-block: {{@root/value}} {{> @partial-block }}' - } - ], - true, - 'before-block: success in-block: success' - ); + expectTemplate('{{#> dude}}in-block: {{@root/value}}{{/dude}}') + .withInput({ value: 'success' }) + .withPartials({ + dude: + 'before-block: {{@root/value}} {{> @partial-block }}' + }) + .toCompileTo('before-block: success in-block: success'); }); it('should allow the #each-helper to be used along with partial-blocks', function() { - shouldCompileToWithPartials( - '', - [ - { value: ['a', 'b', 'c'] }, - {}, - { - list: - '{{#each .}}{{> @partial-block}}{{/each}}' - } - ], - true, - '' - ); + expectTemplate( + '' + ) + .withInput({ + value: ['a', 'b', 'c'] + }) + .withPartials({ + list: + '{{#each .}}{{> @partial-block}}{{/each}}' + }) + .toCompileTo( + '' + ); }); + it('should render block from partial with context (twice)', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { - dude: - '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' - } - ], - true, - 'success success' - ); + expectTemplate('{{#> dude}}{{value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: + '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}' + }) + .toCompileTo('success success'); }); + it('should render block from partial with context', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{../context/value}}{{/dude}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{#with context}}{{> @partial-block }}{{/with}}' } - ], - true, - 'success' - ); + expectTemplate('{{#> dude}}{{../context/value}}{{/dude}}') + .withInput({ context: { value: 'success' } }) + .withPartials({ + dude: '{{#with context}}{{> @partial-block }}{{/with}}' + }) + .toCompileTo('success'); }); + it('should render block from partial with block params', function() { - shouldCompileToWithPartials( - '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}', - [ - { context: { value: 'success' } }, - {}, - { dude: '{{> @partial-block }}' } - ], - true, - 'success' - ); + expectTemplate( + '{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}' + ) + .withInput({ context: { value: 'success' } }) + .withPartials({ dude: '{{> @partial-block }}' }) + .toCompileTo('success'); }); + it('should render nested partial blocks', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}}{{/nested}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}}{{/nested}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks at different nesting levels', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks at different nesting levels (twice)', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}', - nested: '{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}', + nested: '{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); + it('should render nested partial blocks (twice at each level)', function() { - shouldCompileToWithPartials( - '', - [ - { value: 'success' }, - {}, - { - outer: - '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}', - nested: '{{> @partial-block}}{{> @partial-block}}' - } - ], - true, - '' - ); + expectTemplate('') + .withInput({ value: 'success' }) + .withPartials({ + outer: + '{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}', + nested: '{{> @partial-block}}{{> @partial-block}}' + }) + .toCompileTo( + '' + ); }); }); describe('inline partials', function() { it('should define inline partials for template', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ).toCompileTo('success'); }); + it('should overwrite multiple partials in the same template', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ).toCompileTo('success'); }); + it('should define inline partials for block', function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', - {}, - 'success' - ); - shouldThrow( - function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', - {}, - 'success' - ); - }, - Error, - /myPartial could not/ - ); + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' + ).toCompileTo('success'); + + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}' + ).toThrow(Error, /myPartial could not/); }); + it('should override global partials', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', - { - hash: {}, - partials: { - myPartial: function() { - return 'fail'; - } + expectTemplate( + '{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}' + ) + .withPartials({ + myPartial: function() { + return 'fail'; } - }, - 'success' - ); + }) + .toCompileTo('success'); }); + it('should override template partials', function() { - shouldCompileTo( - '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', - {}, - 'success' - ); + expectTemplate( + '{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}' + ).toCompileTo('success'); }); + it('should override partials down the entire stack', function() { - shouldCompileTo( - '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', - {}, - 'success' - ); + expectTemplate( + '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}' + ).toCompileTo('success'); }); it('should define inline partials for partial call', function() { - shouldCompileToWithPartials( - '{{#*inline "myPartial"}}success{{/inline}}{{> dude}}', - [{}, {}, { dude: '{{> myPartial }}' }], - true, - 'success' - ); + expectTemplate('{{#*inline "myPartial"}}success{{/inline}}{{> dude}}') + .withPartials({ dude: '{{> myPartial }}' }) + .toCompileTo('success'); }); + it('should define inline partials in partial block call', function() { - shouldCompileToWithPartials( - '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}', - [{}, {}, { dude: '{{> myPartial }}' }], - true, - 'success' - ); + expectTemplate( + '{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}' + ) + .withPartials({ dude: '{{> myPartial }}' }) + .toCompileTo('success'); }); + it('should render nested inline partials', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'success' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo('success'); }); + it('should render nested inline partials with partial-blocks on different nesting levels', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{>@partial-block}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'successsuccess' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo( + 'successsuccess' + ); }); + it('should render nested inline partials (twice at each level)', function() { - shouldCompileToWithPartials( + expectTemplate( '{{#*inline "outer"}}{{#>inner}}{{>@partial-block}} {{>@partial-block}}{{/inner}}{{/inline}}' + '{{#*inline "inner"}}{{>@partial-block}}{{>@partial-block}}{{/inline}}' + - '{{#>outer}}{{value}}{{/outer}}', - [{ value: 'success' }, {}, {}], - true, - 'success successsuccess success' - ); + '{{#>outer}}{{value}}{{/outer}}' + ) + .withInput({ value: 'success' }) + .toCompileTo( + 'success successsuccess success' + ); }); }); @@ -720,125 +562,119 @@ describe('partials', function() { 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' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartial('dude', '{{name}}\n') + .toCompileTo('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' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' + }) + .toCompileTo( + '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' - ); + expectTemplate('Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}') + .withInput({ + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{name}}\n {{> url}}', + url: '{{url}}!\n' + }) + .withCompileOptions({ preventIndent: true }) + .toCompileTo( + '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 ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withCompileOptions({ compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); + it('partials can access parents with custom context', function() { - var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}'; - var partial = '{{name}} ({{url}}) {{root}} '; - var hash = { - root: 'yes', - dudes: [ - { name: 'Yehuda', url: 'http://yehuda' }, - { name: 'Alan', url: 'http://alan' } - ] - }; - shouldCompileToWithPartials( - string, - [hash, {}, { dude: partial }, true], - true, - 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withCompileOptions({ compat: true }) + .toCompileTo( + '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 ' - ); + expectTemplate('Dudes: {{#dudes}}{{> dude}}{{/dudes}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ dude: '{{name}} ({{url}}) {{root}} ' }) + .withRuntimeOptions({ data: false }) + .withCompileOptions({ data: false, compat: true }) + .toCompileTo( + '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 ' - ); + expectTemplate('Dudes: {{> dude}}') + .withInput({ + root: 'yes', + dudes: [ + { name: 'Yehuda', url: 'http://yehuda' }, + { name: 'Alan', url: 'http://alan' } + ] + }) + .withPartials({ + dude: '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}' + }) + .withCompileOptions({ compat: true }) + .toCompileTo( + 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ' + ); }); }); }); diff --git a/spec/regressions.js b/spec/regressions.js index f6147ad6e..86d4634d2 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -1,61 +1,53 @@ describe('Regressions', function() { it('GH-94: Cannot read property of undefined', function() { - var data = { - books: [ - { - title: 'The origin of species', - author: { - name: 'Charles Darwin' + expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}') + .withInput({ + books: [ + { + title: 'The origin of species', + author: { + name: 'Charles Darwin' + } + }, + { + title: 'Lazarillo de Tormes' } - }, - { - title: 'Lazarillo de Tormes' - } - ] - }; - var string = '{{#books}}{{title}}{{author.name}}{{/books}}'; - shouldCompileTo( - string, - data, - 'The origin of speciesCharles DarwinLazarillo de Tormes', - 'Renders without an undefined property error' - ); + ] + }) + .withMessage('Renders without an undefined property error') + .toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes'); }); it("GH-150: Inverted sections print when they shouldn't", function() { var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; - shouldCompileTo( - string, - {}, - 'not set :: ', - "inverted sections run when property isn't present in context" - ); - shouldCompileTo( - string, - { set: undefined }, - 'not set :: ', - 'inverted sections run when property is undefined' - ); - shouldCompileTo( - string, - { set: false }, - 'not set :: ', - 'inverted sections run when property is false' - ); - shouldCompileTo( - string, - { set: true }, - ' :: set', - "inverted sections don't run when property is true" - ); + expectTemplate(string) + .withMessage( + "inverted sections run when property isn't present in context" + ) + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: undefined }) + .withMessage('inverted sections run when property is undefined') + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: false }) + .withMessage('inverted sections run when property is false') + .toCompileTo('not set :: '); + + expectTemplate(string) + .withInput({ set: true }) + .withMessage("inverted sections don't run when property is true") + .toCompileTo(' :: set'); }); 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'); + expectTemplate('{{arr.[0]}}, {{arr.[1]}}') + .withInput({ arr: [1, 2] }) + .withMessage('it works as expected') + .toCompileTo('1, 2'); }); it("bug reported by @fat where lambdas weren't being properly resolved", function() { @@ -73,6 +65,7 @@ describe('Regressions', function() { '\n' + 'Nothing to check out...\n' + '{{/hasThings}}'; + var data = { thing: function() { return 'blah'; @@ -95,25 +88,22 @@ describe('Regressions', function() { '
  • @dhg
  • \n' + '
  • @sayrer
  • \n' + '.\n'; - shouldCompileTo(string, data, output); + + expectTemplate(string) + .withInput(data) + .toCompileTo(output); }); it('GH-408: Multiple loops fail', function() { - var context = [ - { name: 'John Doe', location: { city: 'Chicago' } }, - { name: 'Jane Doe', location: { city: 'New York' } } - ]; - - var template = CompilerContext.compile( + expectTemplate( '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}' - ); - - var result = template(context); - equals( - result, - 'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe', - 'It should output multiple times' - ); + ) + .withInput([ + { name: 'John Doe', location: { city: 'Chicago' } }, + { name: 'Jane Doe', location: { city: 'New York' } } + ]) + .withMessage('It should output multiple times') + .toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe'); }); it('GS-428: Nested if else rendering', function() { @@ -131,259 +121,242 @@ describe('Regressions', function() { } }; - shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); - shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); + expectTemplate(succeedingTemplate) + .withHelpers(helpers) + .toCompileTo(' Expected '); + + expectTemplate(failingTemplate) + .withHelpers(helpers) + .toCompileTo(' Expected '); }); it('GH-458: Scoped this identifier', function() { - shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar'); + expectTemplate('{{./foo}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); }); it('GH-375: Unicode line terminators', function() { - shouldCompileTo('\u2028', {}, '\u2028'); + expectTemplate('\u2028').toCompileTo('\u2028'); }); it('GH-534: Object prototype aliases', function() { /* eslint-disable no-extend-native */ Object.prototype[0xd834] = true; - shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar'); + expectTemplate('{{foo}}') + .withInput({ foo: 'bar' }) + .toCompileTo('bar'); delete Object.prototype[0xd834]; /* eslint-enable no-extend-native */ }); it('GH-437: Matching escaping', function() { - shouldThrow(function() { - CompilerContext.compile('{{{a}}'); - }, Error); - shouldThrow(function() { - CompilerContext.compile('{{a}}}'); - }, Error); + expectTemplate('{{{a}}').toThrow(Error, /Parse error on/); + expectTemplate('{{a}}}').toThrow(Error, /Parse error on/); }); 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'); + expectTemplate('{{arr}}') + .withInput(data) + .withMessage('it works as expected') + .toCompileTo(data.arr.toString()); }); 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 - }; - - shouldCompileTo( - string, - data, - 'Hello Chris. You have just won $10000! Well, $6000, after taxes.', - 'the hello world mustache example works' - ); + expectTemplate( + 'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}' + ) + .withInput({ + name: 'Chris', + value: 10000, + taxed_value: 10000 - 10000 * 0.4, + in_ca: true + }) + .withMessage('the hello world mustache example works') + .toCompileTo( + 'Hello Chris. You have just won $10000! Well, $6000, after taxes.' + ); }); it('GH-731: zero context rendering', function() { - shouldCompileTo( - '{{#foo}} This is {{bar}} ~ {{/foo}}', - { foo: 0, bar: 'OK' }, - ' This is ~ ' - ); + expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}') + .withInput({ + foo: 0, + bar: 'OK' + }) + .toCompileTo(' This is ~ '); }); it('GH-820: zero pathed rendering', function() { - shouldCompileTo('{{foo.bar}}', { foo: 0 }, ''); + expectTemplate('{{foo.bar}}') + .withInput({ foo: 0 }) + .toCompileTo(''); }); it('GH-837: undefined values for helpers', function() { - var helpers = { - str: function(value) { - return value + ''; - } - }; - - shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined'); + expectTemplate('{{str bar.baz}}') + .withHelpers({ + str: function(value) { + return value + ''; + } + }) + .toCompileTo('undefined'); }); it('GH-926: Depths and de-dupe', function() { - var context = { - name: 'foo', - data: [1], - notData: [1] - }; - - var template = CompilerContext.compile( + expectTemplate( '{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}' - ); - - var result = template(context); - equals(result, 'foo'); + ) + .withInput({ + name: 'foo', + data: [1], + notData: [1] + }) + .toCompileTo('foo'); }); it('GH-1021: Each empty string key', function() { - var data = { - '': 'foo', - name: 'Chris', - value: 10000 - }; - - shouldCompileTo( - '{{#each data}}Key: {{@key}}\n{{/each}}', - { data: data }, - 'Key: \nKey: name\nKey: value\n' - ); + expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}') + .withInput({ + data: { + '': 'foo', + name: 'Chris', + value: 10000 + } + }) + .toCompileTo('Key: \nKey: name\nKey: value\n'); }); it('GH-1054: Should handle simple safe string responses', function() { - var root = '{{#wrap}}{{>partial}}{{/wrap}}'; - var partials = { - partial: '{{#wrap}}{{/wrap}}' - }; - var helpers = { - wrap: function(options) { - return new Handlebars.SafeString(options.fn()); - } - }; - - shouldCompileToWithPartials( - root, - [{}, helpers, partials], - true, - '' - ); + expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') + .withHelpers({ + wrap: function(options) { + return new Handlebars.SafeString(options.fn()); + } + }) + .withPartials({ + partial: '{{#wrap}}{{/wrap}}' + }) + .toCompileTo(''); }); it('GH-1065: Sparse arrays', function() { var array = []; array[1] = 'foo'; array[3] = 'bar'; - shouldCompileTo( - '{{#each array}}{{@index}}{{.}}{{/each}}', - { array: array }, - '1foo3bar' - ); + expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}') + .withInput({ array: array }) + .toCompileTo('1foo3bar'); }); it('GH-1093: Undefined helper context', function() { - var obj = { foo: undefined, bar: 'bat' }; - var helpers = { - helper: function() { - // It's valid to execute a block against an undefined context, but - // helpers can not do so, so we expect to have an empty object here; - for (var name in this) { - if (Object.prototype.hasOwnProperty.call(this, name)) { - return 'found'; + expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}') + .withInput({ obj: { foo: undefined, bar: 'bat' } }) + .withHelpers({ + helper: function() { + // It's valid to execute a block against an undefined context, but + // helpers can not do so, so we expect to have an empty object here; + for (var name in this) { + if (Object.prototype.hasOwnProperty.call(this, name)) { + return 'found'; + } } + // And to make IE happy, check for the known string as length is not enumerated. + return this === 'bat' ? 'found' : 'not'; } - // And to make IE happy, check for the known string as length is not enumerated. - return this === 'bat' ? 'found' : 'not'; - } - }; - - shouldCompileTo( - '{{#each obj}}{{{helper}}}{{.}}{{/each}}', - [{ obj: obj }, helpers], - 'notfoundbat' - ); + }) + .toCompileTo('notfoundbat'); }); it('should support multiple levels of inline partials', function() { - var string = - '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'doctypelayoutsubcontent' - ); + expectTemplate( + '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}' + ) + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' + }) + .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1089: should support failover content in multiple levels of inline partials', function() { - var string = '{{#> layout}}{{/layout}}'; - var partials = { - doctype: 'doctype{{> content}}', - layout: - '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'doctypelayoutsubcontent' - ); + expectTemplate('{{#> layout}}{{/layout}}') + .withPartials({ + doctype: 'doctype{{> content}}', + layout: + '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' + }) + .toCompileTo('doctypelayoutsubcontent'); }); + it('GH-1099: should support greater than 3 nested levels of inline partials', function() { - var string = '{{#> layout}}Outer{{/layout}}'; - var partials = { - layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', - inner: '' - }; - shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer'); + expectTemplate('{{#> layout}}Outer{{/layout}}') + .withPartials({ + layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', + inner: '' + }) + .toCompileTo('Outer'); }); it('GH-1135 : Context handling within each iteration', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - myif: function(conditional, options) { - if (conditional) { - return options.fn(this); - } else { - return options.inverse(this); - } - } - }; - - shouldCompileTo( + expectTemplate( '{{#each array}}\n' + ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' + ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' + - '{{/each}}', - [obj, helpers], - ' 1. IF: John--\n' + ' 2. MYIF: John==\n' - ); + '{{/each}}' + ) + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + myif: function(conditional, options) { + if (conditional) { + return options.fn(this); + } else { + return options.inverse(this); + } + } + }) + .toCompileTo(' 1. IF: John--\n' + ' 2. MYIF: John==\n'); }); it('GH-1186: Support block params for existing programs', function() { - var string = + expectTemplate( '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + - '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + - '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'; - - shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', ''); + '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + + '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' + ) + .withInput({ + listOne: ['a'], + listTwo: ['b'] + }) + .withMessage('') + .toCompileTo('ab'); }); it('GH-1319: "unless" breaks when "each" value equals "null"', function() { - var string = - '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'; - shouldCompileTo( - string, - { value: 'parent', list: [null, 'a'] }, - 'parent=parent parent=parent ', - '' - ); + expectTemplate( + '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}' + ) + .withInput({ + value: 'parent', + list: [null, 'a'] + }) + .withMessage('') + .toCompileTo('parent=parent parent=parent '); }); it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { - var string = 'template {{>partial}} template'; - var partials = { - partialWithBlock: - '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', - partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' - }; - shouldCompileToWithPartials( - string, - [{}, {}, partials], - true, - 'template block partial block template' - ); + expectTemplate('template {{>partial}} template') + .withPartials({ + partialWithBlock: + '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', + partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' + }) + .toCompileTo('template block partial block template'); }); describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() { @@ -482,14 +455,14 @@ describe('Regressions', function() { }); it('should allow hash with protected array names', function() { - var obj = { array: [1], name: 'John' }; - var helpers = { - helpa: function(options) { - return options.hash.length; - } - }; - - shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo'); + expectTemplate('{{helpa length="foo"}}') + .withInput({ array: [1], name: 'John' }) + .withHelpers({ + helpa: function(options) { + return options.hash.length; + } + }) + .toCompileTo('foo'); }); describe('GH-1598: Performance degradation for partials since v4.3.0', function() { diff --git a/spec/security.js b/spec/security.js index 1b345f0cc..00eb1318b 100644 --- a/spec/security.js +++ b/spec/security.js @@ -20,36 +20,19 @@ describe('security issues', function() { }); it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() { - shouldCompileTo( - '{{constructor.name}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); - shouldCompileTo( - '{{lookup (lookup this "constructor") "name"}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); + expectTemplate('{{constructor.name}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); + + expectTemplate('{{lookup (lookup this "constructor") "name"}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); }); it('should allow the "constructor" property to be accessed if it is an "own property"', function() { - shouldCompileTo( - '{{lookup (lookup this "constructor") "name"}}', - { - constructor: { - name: 'here we go' - } - }, - 'here we go' - ); + expectTemplate('{{lookup (lookup this "constructor") "name"}}') + .withInput({ constructor: { name: 'here we go' } }) + .toCompileTo('here we go'); }); }); @@ -60,19 +43,13 @@ describe('security issues', function() { describe('without the option "allowExplicitCallOfHelperMissing"', function() { it('should throw an exception when calling "{{helperMissing}}" ', function() { - shouldThrow(function() { - var template = Handlebars.compile('{{helperMissing}}'); - template({}); - }, Error); + expectTemplate('{{helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { - shouldThrow(function() { - var template = Handlebars.compile( - '{{#helperMissing}}{{/helperMissing}}' - ); - template({}); - }, Error); + expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error); }); + it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; expect(function() { @@ -85,17 +62,15 @@ describe('security issues', function() { }).to.throw(Error); expect(functionCalls.length).to.equal(0); }); + it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { - shouldThrow(function() { - var template = Handlebars.compile( - '{{#blockHelperMissing .}}{{/blockHelperMissing}}' - ); - template({ + expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}') + .withInput({ fn: function() { return 'functionInData'; } - }); - }, Error); + }) + .toThrow(Error); }); }); @@ -104,12 +79,14 @@ describe('security issues', function() { var template = Handlebars.compile('{{helperMissing}}'); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { var template = Handlebars.compile( '{{#helperMissing}}{{/helperMissing}}' ); template({}, { allowCallsToHelperMissing: true }); }); + it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { var functionCalls = []; var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); @@ -123,6 +100,7 @@ describe('security issues', function() { ); equals(functionCalls.length, 1); }); + it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { var template = Handlebars.compile( '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}' @@ -320,6 +298,10 @@ describe('security issues', function() { checkProtoPropertyAccess({ compat: true }); }); + describe('in strict-mode', function() { + checkProtoPropertyAccess({ strict: true }); + }); + function checkProtoPropertyAccess(compileOptions) { it('should be prohibited by default and log a warning', function() { var spy = sinon.spy(console, 'error'); @@ -418,6 +400,28 @@ describe('security issues', function() { }); }); }); + + describe('escapes template variables', function() { + it('in compat mode', function() { + expectTemplate("{{'a\\b'}}") + .withCompileOptions({ compat: true }) + .withInput({ 'a\\b': 'c' }) + .toCompileTo('c'); + }); + + it('in default mode', function() { + expectTemplate("{{'a\\b'}}") + .withCompileOptions() + .withInput({ 'a\\b': 'c' }) + .toCompileTo('c'); + }); + it('in default mode', function() { + expectTemplate("{{'a\\b'}}") + .withCompileOptions({ strict: true }) + .withInput({ 'a\\b': 'c' }) + .toCompileTo('c'); + }); + }); }); function wrapToAdjustContainer(precompiledTemplateFunction) { diff --git a/spec/spec.js b/spec/spec.js index ff4d7bc98..8b4ea99fe 100644 --- a/spec/spec.js +++ b/spec/spec.js @@ -40,22 +40,12 @@ describe('spec', function() { /* 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 + '"' - ); - } + expectTemplate(test.template) + .withInput(data) + .withPartials(test.partials || {}) + .withCompileOptions({ compat: true }) + .withMessage(test.desc + ' "' + test.template + '"') + .toCompileTo(test.expected); }); }); }); diff --git a/spec/strict.js b/spec/strict.js index 680bbc0d8..8e48fab5e 100644 --- a/spec/strict.js +++ b/spec/strict.js @@ -3,161 +3,107 @@ 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/ - ); + expectTemplate('{{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(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'); + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: { bar: 'foo' } }) + .toCompileTo('foo'); - shouldThrow( - function() { - template({ hello: {} }); - }, - Exception, - /"bar" not defined in/ - ); + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: {} }) + .toThrow(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 handle explicit undefined', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: { bar: undefined } }) + .toCompileTo(''); }); + 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/ - ); + expectTemplate('{{hello}}') + .withCompileOptions({ + strict: true, + knownHelpersOnly: true + }) + .toThrow(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 context', function() { + expectTemplate('{{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Error); }); it('should error on missing data lookup', function() { - var template = CompilerContext.compile('{{@hello}}', { strict: true }); - equals(template(undefined, { data: { hello: 'foo' } }), 'foo'); + var xt = expectTemplate('{{@hello}}').withCompileOptions({ + strict: true + }); - shouldThrow(function() { - template(); - }, Error); + xt.toThrow(Error); + + xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo'); }); 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/ - ); + expectTemplate('{{hello foo}}') + .withCompileOptions({ strict: true }) + .withInput({ foo: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{#hello foo}}{{/hello}}') + .withCompileOptions({ strict: true }) + .withInput({ foo: true }) + .toThrow(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/ - ); + expectTemplate('{{#hello}}{{/hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{^hello}}{{/hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, /"hello" not defined in/); + + expectTemplate('{{#hello.bar}}{{/hello.bar}}') + .withCompileOptions({ strict: true }) + .withInput({ hello: {} }) + .toThrow(Exception, /"bar" not defined in/); }); it('should allow undefined parameters when passed to helpers', function() { - var template = CompilerContext.compile( - '{{#unless foo}}success{{/unless}}', - { strict: true } - ); - equals(template({}), 'success'); + expectTemplate('{{#unless foo}}success{{/unless}}') + .withCompileOptions({ strict: true }) + .toCompileTo('success'); }); it('should allow undefined hash when passed to helpers', function() { - var template = CompilerContext.compile('{{helper value=@foo}}', { - strict: true - }); - var helpers = { - helper: function(options) { - equals('value' in options.hash, true); - equals(options.hash.value, undefined); - return 'success'; - } - }; - equals(template({}, { helpers: helpers }), 'success'); + expectTemplate('{{helper value=@foo}}') + .withCompileOptions({ + strict: true + }) + .withHelpers({ + helper: function(options) { + equals('value' in options.hash, true); + equals(options.hash.value, undefined); + return 'success'; + } + }) + .toCompileTo('success'); }); it('should show error location on missing property lookup', function() { - shouldThrow( - function() { - var template = CompilerContext.compile('\n\n\n {{hello}}', { - strict: true - }); - template({}); - }, - Exception, - '"hello" not defined in [object Object] - 4:5' - ); + expectTemplate('\n\n\n {{hello}}') + .withCompileOptions({ strict: true }) + .toThrow(Exception, '"hello" not defined in [object Object] - 4:5'); }); it('should error contains correct location properties on missing property lookup', function() { @@ -177,54 +123,42 @@ describe('strict', function() { describe('assume objects', function() { it('should ignore missing property', function() { - var template = CompilerContext.compile('{{hello}}', { - assumeObjects: true - }); - - equal(template({}), ''); + expectTemplate('{{hello}}') + .withCompileOptions({ assumeObjects: true }) + .toCompileTo(''); }); - it('should ignore missing child', function() { - var template = CompilerContext.compile('{{hello.bar}}', { - assumeObjects: true - }); - equal(template({ hello: {} }), ''); + it('should ignore missing child', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .withInput({ hello: {} }) + .toCompileTo(''); }); - it('should error on missing object', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{hello.bar}}', { - assumeObjects: true - }); - template({}); - }, Error); + it('should error on missing object', function() { + expectTemplate('{{hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .toThrow(Error); }); - it('should error on missing context', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{hello}}', { - assumeObjects: true - }); - template(); - }, Error); + it('should error on missing context', function() { + expectTemplate('{{hello}}') + .withCompileOptions({ assumeObjects: true }) + .withInput(undefined) + .toThrow(Error); }); it('should error on missing data lookup', function() { - shouldThrow(function() { - var template = CompilerContext.compile('{{@hello.bar}}', { - assumeObjects: true - }); - - template(); - }, Error); + expectTemplate('{{@hello.bar}}') + .withCompileOptions({ assumeObjects: true }) + .withInput(undefined) + .toThrow(Error); }); it('should execute blockHelperMissing', function() { - var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', { - assumeObjects: true - }); - - equals(template({}), 'foo'); + expectTemplate('{{^hello}}foo{{/hello}}') + .withCompileOptions({ assumeObjects: true }) + .toCompileTo('foo'); }); }); }); diff --git a/spec/string-params.js b/spec/string-params.js index 9f83611a4..c4b9a27b7 100644 --- a/spec/string-params.js +++ b/spec/string-params.js @@ -1,151 +1,117 @@ describe('string params mode', function() { it('arguments to helpers can be retrieved from options hash in string form', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}', { - stringParams: true - }); - - var helpers = { - wycats: function(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' - ); + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun) { + return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun; + } + }) + .withMessage('String parameters output') + .toCompileTo('HELP ME MY BOSS is.a slave.driver'); }); 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 + - ' ' + - noun + - ': ' + - options.fn(this) - ); - } - }; - - var result = template({}, { helpers: helpers }); - - equals( - result, - 'HELP ME MY BOSS is.a slave.driver: help :(', - 'String parameters output' - ); + expectTemplate('{{#wycats is.a slave.driver}}help :({{/wycats}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ': ' + + options.fn(this) + ); + } + }) + .withMessage('String parameters output') + .toCompileTo('HELP ME MY BOSS is.a slave.driver: help :('); }); 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 - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template( - { + expectTemplate('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + ); + }, + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ dale: {}, need: 'need-a' - }, - { helpers: helpers } - ); - - equals( - result, - 'STOP ME FROM READING HACKER NEWS I need-a dad.joke', - 'Proper context variable output' - ); + }) + .withMessage('Proper context variable output') + .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke'); }); 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], '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'); + expectTemplate("{{tomdale 'need' dad.joke true false}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, trueBool, falseBool, options) { + equal(options.types[0], 'StringLiteral', 'the string type is passed'); + equal( + options.types[1], + 'PathExpression', + 'the expression type is passed' + ); + equal( + options.types[2], + 'BooleanLiteral', + 'the expression type is passed' + ); + equal(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'; + } + }) + .toCompileTo('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 } - ); - - var helpers = { - tomdale: function(exclamation, options) { - equal(exclamation, 'he.says'); - equal(options.types[0], 'PathExpression'); - - 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'; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, 'Helper called'); + expectTemplate("{{tomdale he.says desire='need' noun=dad.joke bool=true}}") + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(exclamation, options) { + equal(exclamation, 'he.says'); + equal(options.types[0], 'PathExpression'); + + 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'; + } + }) + .toCompileTo('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 } - ); - var context = { dale: {} }; var helpers = { @@ -165,82 +131,77 @@ describe('string params mode', function() { } }; - var result = template(context, { helpers: helpers }); - equal(result, 'Helper called'); + expectTemplate( + "{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}" + ) + .withCompileOptions({ stringParams: true }) + .withHelpers(helpers) + .withInput(context) + .toCompileTo('Helper called'); }); 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 + - ' ' + - options.fn(this) - ); - }, - - with: function(context, options) { - return options.fn(options.contexts[0][context]); - } - }; - - var result = template( - { + expectTemplate( + '{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + tomdale: function(desire, noun, options) { + return ( + 'STOP ME FROM READING HACKER NEWS I ' + + options.contexts[0][desire] + + ' ' + + noun + + ' ' + + options.fn(this) + ); + }, + + with: function(context, options) { + return options.fn(options.contexts[0][context]); + } + }) + .withInput({ dale: {}, need: 'need-a' - }, - { helpers: helpers } - ); - - equals( - result, - 'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot', - 'Proper context variable output' - ); + }) + .withMessage('Proper context variable output') + .toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke wot'); }); it('with nested block ambiguous', function() { - var template = CompilerContext.compile( - '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', - { stringParams: true } - ); - - var helpers = { - with: function() { - return 'WITH'; - }, - view: function() { - return 'VIEW'; - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'WITH'); + expectTemplate( + '{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}' + ) + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + with: function() { + return 'WITH'; + }, + view: function() { + return 'VIEW'; + } + }) + .toCompileTo('WITH'); }); it('should handle DATA', function() { - var template = CompilerContext.compile('{{foo @bar}}', { - stringParams: true - }); - - var helpers = { - foo: function(bar, options) { - equal(bar, '@bar'); - equal(options.types[0], 'PathExpression'); - return 'Foo!'; - } - }; - - var result = template({}, { helpers: helpers }); - equal(result, 'Foo!'); + expectTemplate('{{foo @bar}}') + .withCompileOptions({ + stringParams: true + }) + .withHelpers({ + foo: function(bar, options) { + equal(bar, '@bar'); + equal(options.types[0], 'PathExpression'); + return 'Foo!'; + } + }) + .toCompileTo('Foo!'); }); }); diff --git a/spec/subexpressions.js b/spec/subexpressions.js index 228510511..21b93aab9 100644 --- a/spec/subexpressions.js +++ b/spec/subexpressions.js @@ -1,61 +1,57 @@ describe('subexpressions', function() { it('arg-less helper', function() { - var string = '{{foo (bar)}}!'; - var context = {}; - var helpers = { - foo: function(val) { - return val + val; - }, - bar: function() { - return 'LOL'; - } - }; - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); + expectTemplate('{{foo (bar)}}!') + .withHelpers({ + foo: function(val) { + return val + val; + }, + bar: function() { + return 'LOL'; + } + }) + .toCompileTo('LOLLOL!'); }); it('helper w args', function() { - var string = '{{blog (equal a b)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate('{{blog (equal a b)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); 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 = { - blog: function(val, that, 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!'); + expectTemplate('{{blog baz.bat (equal a b) baz.bar}}') + .withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } }) + .withHelpers({ + blog: function(val, that, theOther) { + return 'val is ' + val + ', ' + that + ' and ' + theOther; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is foo!, true and bar!'); }); it('supports much nesting', function() { - var string = '{{blog (equal (equal true true) true)}}'; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate('{{blog (equal (equal true true) true)}}') + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('GH-800 : Complex subexpressions', function() { @@ -69,20 +65,33 @@ describe('subexpressions', function() { } }; - 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'); + expectTemplate("{{dash 'abc' (concat a b)}}") + .withInput(context) + .withHelpers(helpers) + .toCompileTo('abc-ab'); + + expectTemplate('{{dash d (concat a b)}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('d-ab'); + + expectTemplate('{{dash c.c (concat a b)}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('c-ab'); + + expectTemplate('{{dash (concat a b) c.c}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('ab-c'); + + expectTemplate('{{dash (concat a e.e) c.c}}') + .withInput(context) + .withHelpers(helpers) + .toCompileTo('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 = { equal: function(x, y, options) { @@ -93,200 +102,177 @@ describe('subexpressions', function() { return x === y; } }; - shouldCompileTo(string, [{}, helpers], 'true'); + expectTemplate('{{equal (equal true true) true}}') + .withHelpers(helpers) + .toCompileTo('true'); }); it('with hashes', function() { - var string = "{{blog (equal (equal true true) true fun='yes')}}"; - - var context = { bar: 'LOL' }; - var helpers = { - blog: function(val) { - return 'val is ' + val; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [context, helpers], 'val is true'); + expectTemplate("{{blog (equal (equal true true) true fun='yes')}}") + .withInput({ bar: 'LOL' }) + .withHelpers({ + blog: function(val) { + return 'val is ' + val; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); it('as hashes', function() { - var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}"; - - var helpers = { - blog: function(options) { - return 'val is ' + options.hash.fun; - }, - equal: function(x, y) { - return x === y; - } - }; - shouldCompileTo(string, [{}, helpers], 'val is true'); + expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}") + .withHelpers({ + blog: function(options) { + return 'val is ' + options.hash.fun; + }, + equal: function(x, y) { + return x === y; + } + }) + .toCompileTo('val is true'); }); 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 placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); - return new Handlebars.SafeString( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - shouldCompileTo( - string, - [{}, helpers], - '' - ); + expectTemplate( + '{{input aria-label=(t "Name") placeholder=(t "Example User")}}' + ) + .withHelpers({ + 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( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) + .toCompileTo(''); }); 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( - '' - ); - }, - t: function(defaultString) { - return new Handlebars.SafeString(defaultString); - } - }; - shouldCompileTo( - string, - [context, helpers], - '' - ); + expectTemplate( + '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}' + ) + .withInput({ + item: { + field: 'Name', + placeholder: 'Example User' + } + }) + .withHelpers({ + 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( + '' + ); + }, + t: function(defaultString) { + return new Handlebars.SafeString(defaultString); + } + }) + .toCompileTo(''); }); 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], - 'SubExpression', - 'string params for outer helper processed correctly' - ); - equals( - options.types[1], - 'PathExpression', - 'string params for outer helper processed correctly' - ); - return a + b; - }, - - blorg: function(a, options) { - equals( - options.types.length, - 1, - 'string params for inner helper processed correctly' - ); - equals( - options.types[0], - 'PathExpression', - 'string params for inner helper processed correctly' - ); - return a; - } - }; + expectTemplate('{{snog (blorg foo x=y) yeah a=b}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + snog: function(a, b, options) { + equals(a, 'foo'); + equals( + options.types.length, + 2, + 'string params for outer helper processed correctly' + ); + equals( + options.types[0], + 'SubExpression', + 'string params for outer helper processed correctly' + ); + equals( + options.types[1], + 'PathExpression', + 'string params for outer helper processed correctly' + ); + return a + b; + }, - var result = template( - { + blorg: function(a, options) { + equals( + options.types.length, + 1, + 'string params for inner helper processed correctly' + ); + equals( + options.types[0], + 'PathExpression', + 'string params for inner helper processed correctly' + ); + return a; + } + }) + .withInput({ foo: {}, yeah: {} - }, - { helpers: helpers } - ); - - equals(result, 'fooyeah'); + }) + .toCompileTo('fooyeah'); }); it('as hashes in string params mode', function() { - var template = CompilerContext.compile('{{blog fun=(bork)}}', { - stringParams: true - }); - - var helpers = { - blog: function(options) { - equals(options.hashTypes.fun, 'SubExpression'); - return 'val is ' + options.hash.fun; - }, - bork: function() { - return 'BORK'; - } - }; - - var result = template({}, { helpers: helpers }); - equals(result, 'val is BORK'); + expectTemplate('{{blog fun=(bork)}}') + .withCompileOptions({ stringParams: true }) + .withHelpers({ + blog: function(options) { + equals(options.hashTypes.fun, 'SubExpression'); + return 'val is ' + options.hash.fun; + }, + bork: function() { + return 'BORK'; + } + }) + .toCompileTo('val is BORK'); }); it('subexpression functions on the context', function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: function() { - return 'LOL'; - } - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: function() { + return 'LOL'; + } + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) + .toCompileTo('LOLLOL!'); }); it("subexpressions can't just be property lookups", function() { - var string = '{{foo (bar)}}!'; - var context = { - bar: 'LOL' - }; - var helpers = { - foo: function(val) { - return val + val; - } - }; - shouldThrow(function() { - shouldCompileTo(string, [context, helpers], 'LOLLOL!'); - }); + expectTemplate('{{foo (bar)}}!') + .withInput({ + bar: 'LOL' + }) + .withHelpers({ + foo: function(val) { + return val + val; + } + }) + .toThrow(); }); }); diff --git a/spec/track-ids.js b/spec/track-ids.js index f7ad7abda..033e7b176 100644 --- a/spec/track-ids.js +++ b/spec/track-ids.js @@ -5,216 +5,184 @@ describe('track ids', function() { }); it('should not include anything without the flag', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}'); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids, undefined); - equal(options.hashIds, undefined); - - return 'success'; - } - }; - - equals(template({}, { helpers: helpers }), 'success'); + expectTemplate('{{wycats is.a slave.driver}}') + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids, undefined); + equal(options.hashIds, undefined); + + return 'success'; + } + }) + .toCompileTo('success'); }); - it('should include argument ids', function() { - var template = CompilerContext.compile('{{wycats is.a slave.driver}}', { - trackIds: true - }); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo slave.driver:bar' - ); + it('should include argument ids', function() { + expectTemplate('{{wycats is.a slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should include hash ids', function() { - var template = CompilerContext.compile( - '{{wycats bat=is.a baz=slave.driver}}', - { trackIds: true } - ); - - var helpers = { - wycats: function(options) { - equal(options.hashIds.bat, 'is.a'); - equal(options.hashIds.baz, 'slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.hashIds.bat + - ':' + - options.hash.bat + - ' ' + - options.hashIds.baz + - ':' + - options.hash.baz - ); - } - }; - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo slave.driver:bar' - ); + it('should include hash ids', function() { + expectTemplate('{{wycats bat=is.a baz=slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(options) { + equal(options.hashIds.bat, 'is.a'); + equal(options.hashIds.baz, 'slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.hashIds.bat + + ':' + + options.hash.bat + + ' ' + + options.hashIds.baz + + ':' + + options.hash.baz + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar'); }); - it('should note ../ and ./ references', function() { - var template = CompilerContext.compile( - '{{wycats ./is.a ../slave.driver this.is.a this}}', - { trackIds: true } - ); - var helpers = { - wycats: function(passiveVoice, noun, thiz, thiz2, options) { - equal(options.ids[0], 'is.a'); - equal(options.ids[1], '../slave.driver'); - equal(options.ids[2], 'is.a'); - equal(options.ids[3], ''); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS is.a:foo ../slave.driver:undefined' - ); + it('should note ../ and ./ references', function() { + expectTemplate('{{wycats ./is.a ../slave.driver this.is.a this}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, thiz, thiz2, options) { + equal(options.ids[0], 'is.a'); + equal(options.ids[1], '../slave.driver'); + equal(options.ids[2], 'is.a'); + equal(options.ids[3], ''); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS is.a:foo ../slave.driver:undefined'); }); - it('should note @data references', function() { - var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', { - trackIds: true - }); - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], '@is.a'); - equal(options.ids[1], '@slave.driver'); - - return ( - 'HELP ME MY BOSS ' + - options.ids[0] + - ':' + - passiveVoice + - ' ' + - options.ids[1] + - ':' + - noun - ); - } - }; - - equals( - template({}, { helpers: helpers, data: context }), - 'HELP ME MY BOSS @is.a:foo @slave.driver:bar' - ); + it('should note @data references', function() { + expectTemplate('{{wycats @is.a @slave.driver}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], '@is.a'); + equal(options.ids[1], '@slave.driver'); + + return ( + 'HELP ME MY BOSS ' + + options.ids[0] + + ':' + + passiveVoice + + ' ' + + options.ids[1] + + ':' + + noun + ); + } + }) + .withRuntimeOptions({ data: context }) + .toCompileTo('HELP ME MY BOSS @is.a:foo @slave.driver:bar'); }); it('should return null for constants', function() { - var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', { - trackIds: true - }); - - var helpers = { - wycats: function(passiveVoice, noun, options) { - equal(options.ids[0], null); - equal(options.ids[1], null); - equal(options.hashIds.key, null); - - return ( - 'HELP ME MY BOSS ' + - passiveVoice + - ' ' + - noun + - ' ' + - options.hash.key - ); - } - }; - - equals( - template(context, { helpers: helpers }), - 'HELP ME MY BOSS 1 foo false' - ); + expectTemplate('{{wycats 1 "foo" key=false}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + wycats: function(passiveVoice, noun, options) { + equal(options.ids[0], null); + equal(options.ids[1], null); + equal(options.hashIds.key, null); + + return ( + 'HELP ME MY BOSS ' + + passiveVoice + + ' ' + + noun + + ' ' + + options.hash.key + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS 1 foo false'); }); - it('should return true for subexpressions', function() { - var template = CompilerContext.compile('{{wycats (sub)}}', { - trackIds: true - }); - - var helpers = { - sub: function() { - return 1; - }, - wycats: function(passiveVoice, options) { - equal(options.ids[0], true); - - return 'HELP ME MY BOSS ' + passiveVoice; - } - }; - equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1'); + it('should return true for subexpressions', function() { + expectTemplate('{{wycats (sub)}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + sub: function() { + return 1; + }, + wycats: function(passiveVoice, options) { + equal(options.ids[0], true); + + return 'HELP ME MY BOSS ' + passiveVoice; + } + }) + .withInput(context) + .toCompileTo('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' - ); + expectTemplate('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}') + .withCompileOptions({ trackIds: true }) + .withHelpers({ + 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 + ); + } + }) + .withInput(context) + .toCompileTo('HELP ME MY BOSS zomg.a:foo slave.driver:bar'); }); describe('builtin helpers', function() { @@ -229,119 +197,85 @@ describe('track ids', function() { describe('#each', function() { it('should track contextPath for arrays', function() { - var template = CompilerContext.compile( - '{{#each array}}{{wycats name}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { array: [{ name: 'foo' }, { name: 'bar' }] }, - { helpers: helpers } - ), - 'foo:array.0\nbar:array.1\n' - ); + expectTemplate('{{#each array}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('foo:array.0\nbar:array.1\n'); }); + it('should track contextPath for keys', function() { - var template = CompilerContext.compile( - '{{#each object}}{{wycats name}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }, - { helpers: helpers } - ), - 'foo:object.foo\nbar:object.bar\n' - ); + expectTemplate('{{#each object}}{{wycats name}}{{/each}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } }) + .toCompileTo('foo:object.foo\nbar:object.bar\n'); }); + it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', - { trackIds: true } - ); - - equals( - template( - { array: [{ name: 'foo' }, { name: 'bar' }] }, - { helpers: helpers } - ), - 'foo:.array..0\nbar:.array..1\n' - ); + expectTemplate( + '{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('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' - ); + expectTemplate( + '{{#each array as |value|}}{{blockParams value.name}}{{/each}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ array: [{ name: 'foo' }, { name: 'bar' }] }) + .toCompileTo('foo:array.0.name\nbar:array.1.name\n'); }); }); + describe('#with', function() { it('should track contextPath', function() { - var template = CompilerContext.compile( - '{{#with field}}{{wycats name}}{{/with}}', - { trackIds: true } - ); - - equals( - template({ field: { name: 'foo' } }, { helpers: helpers }), - 'foo:field\n' - ); + expectTemplate('{{#with field}}{{wycats name}}{{/with}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: { name: 'foo' } }) + .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', - { trackIds: true } - ); - equals( - template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }), - 'foo:bat.field\n' - ); + it('should handle nesting', function() { + expectTemplate( + '{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}' + ) + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ bat: { field: { name: 'foo' } } }) + .toCompileTo('foo:bat.field\n'); }); }); + describe('#blockHelperMissing', function() { it('should track contextPath for arrays', function() { - var template = CompilerContext.compile( - '{{#field}}{{wycats name}}{{/field}}', - { trackIds: true } - ); - - equals( - template({ field: [{ name: 'foo' }] }, { helpers: helpers }), - 'foo:field.0\n' - ); + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: [{ name: 'foo' }] }) + .toCompileTo('foo:field.0\n'); }); - it('should track contextPath for keys', function() { - var template = CompilerContext.compile( - '{{#field}}{{wycats name}}{{/field}}', - { trackIds: true } - ); - equals( - template({ field: { name: 'foo' } }, { helpers: helpers }), - 'foo:field\n' - ); + it('should track contextPath for keys', function() { + expectTemplate('{{#field}}{{wycats name}}{{/field}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ field: { name: 'foo' } }) + .toCompileTo('foo:field\n'); }); - it('should handle nesting', function() { - var template = CompilerContext.compile( - '{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', - { trackIds: true } - ); - equals( - template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }), - 'foo:bat.field\n' - ); + it('should handle nesting', function() { + expectTemplate('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}') + .withCompileOptions({ trackIds: true }) + .withHelpers(helpers) + .withInput({ bat: { field: { name: 'foo' } } }) + .toCompileTo('foo:bat.field\n'); }); }); }); diff --git a/spec/utils.js b/spec/utils.js index 667aa0ed3..f4a4e3e4f 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -15,11 +15,9 @@ describe('utils', function() { it('it should not escape SafeString properties', function() { var name = new Handlebars.SafeString('Sean O'Malley'); - shouldCompileTo( - '{{name}}', - [{ name: name }], - 'Sean O'Malley' - ); + expectTemplate('{{name}}') + .withInput({ name: name }) + .toCompileTo('Sean O'Malley'); }); }); diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index b1a6db045..f826d95a1 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -2,125 +2,156 @@ describe('whitespace control', function() { it('should strip whitespace around mustache calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo(' {{~foo~}} ', hash, 'bar<'); - shouldCompileTo(' {{~foo}} ', hash, 'bar< '); - shouldCompileTo(' {{foo~}} ', hash, ' bar<'); + expectTemplate(' {{~foo~}} ') + .withInput(hash) + .toCompileTo('bar<'); - shouldCompileTo(' {{~&foo~}} ', hash, 'bar<'); - shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<'); + expectTemplate(' {{~foo}} ') + .withInput(hash) + .toCompileTo('bar< '); - shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4'); + expectTemplate(' {{foo~}} ') + .withInput(hash) + .toCompileTo(' bar<'); + + expectTemplate(' {{~&foo~}} ') + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate(' {{~{foo}~}} ') + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate('1\n{{foo~}} \n\n 23\n{{bar}}4').toCompileTo('1\n23\n4'); }); describe('blocks', function() { it('should strip whitespace around simple block calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar'); - shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar '); - shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar '); - shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar '); + expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') + .withInput(hash) + .toCompileTo('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 ' - ); + expectTemplate(' {{#if foo~}} bar {{/if~}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' {{~#if foo}} bar {{~/if}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' {{#if foo}} bar {{/if}} ') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ') + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ') + .withInput(hash) + .toCompileTo(' abara '); }); + it('should strip whitespace around inverse block calls', function() { - var hash = {}; + expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar'); - shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar'); - shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar '); - shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar '); - shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar '); + expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar '); - shouldCompileTo( - ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', - hash, - 'bar' - ); + expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar '); + + expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo(' bar '); + + expectTemplate( + ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ' + ).toCompileTo('bar'); }); + it('should strip whitespace around complex block calls', function() { var hash = { foo: 'bar<' }; - shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar'); - shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar '); - shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar'); - shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar '); + expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo('bar'); - shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar'); + expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') + .withInput(hash) + .toCompileTo('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<' + expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo(' bar'); + + expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') + .withInput(hash) + .toCompileTo(' bar '); + + expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate( + '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ) + .withInput(hash) + .toCompileTo('bar'); + + expectTemplate( + '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ) + .withInput(hash) + .toCompileTo('bar<'); + + expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo( + 'baz' ); - hash = {}; + expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz '); - shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz'); - shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz '); - shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz'); - shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz '); + expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo( + ' baz' + ); - shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz'); + expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo( + ' baz ' + ); - shouldCompileTo( - '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', - hash, + expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo( 'baz' ); + + expectTemplate( + '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' + ).toCompileTo('baz'); }); }); it('should strip whitespace around partials', function() { - shouldCompileToWithPartials( - 'foo {{~> dude~}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foobar' - ); - shouldCompileToWithPartials( - 'foo {{> dude~}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo bar' - ); - shouldCompileToWithPartials( - 'foo {{> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo bar ' - ); - - shouldCompileToWithPartials( - 'foo\n {{~> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foobar' - ); - shouldCompileToWithPartials( - 'foo\n {{> dude}} ', - [{}, {}, { dude: 'bar' }], - true, - 'foo\n bar' - ); + expectTemplate('foo {{~> dude~}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foobar'); + + expectTemplate('foo {{> dude~}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo bar'); + + expectTemplate('foo {{> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo bar '); + + expectTemplate('foo\n {{~> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foobar'); + + expectTemplate('foo\n {{> dude}} ') + .withPartials({ dude: 'bar' }) + .toCompileTo('foo\n bar'); }); it('should only strip whitespace once', function() { - var hash = { foo: 'bar' }; - - shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar '); + expectTemplate(' {{~foo~}} {{foo}} {{foo}} ') + .withInput({ foo: 'bar' }) + .toCompileTo('barbar bar '); }); }); pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy