Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbe04946ce | |||
| d069c1caf1 | |||
| 6714e07a6a | |||
| dce542c9a6 | |||
| 8a41389ba5 | |||
| 68d8df5a88 | |||
| b2a083136b | |||
| 9f98c16298 | |||
| 45443b4290 | |||
| 8841a5f6d3 | |||
| e0137c26f2 | |||
| e914d6037f | |||
| 7de4b41c34 | |||
| eab1d141cb | |||
| de4414d7fc | |||
| 08fddee033 | |||
| 4512766919 | |||
| e497a35d7f | |||
| 8c9f866655 | |||
| 520e1d5f08 | |||
| 02423780a9 | |||
| be92d2f254 | |||
| 443a613b3a | |||
| 83ee5908f2 | |||
| 8dc3d2517b | |||
| 668c4fb878 | |||
| c65c6cce3f | |||
| 3d3796c1e9 | |||
| 075b354a3b | |||
| 30dbf04781 | |||
| e3a54485db | |||
| 8e23642ea2 | |||
| 88ac06875f | |||
| c68bc08a0d | |||
| 6cfbc2653a | |||
| b65135acef | |||
| e2f63da5c0 | |||
| 78e7e28ff9 | |||
| 03d387bf8e | |||
| e0f50b4eec | |||
| 9ed9418488 | |||
| ef0fc290b9 | |||
| edc65b5c19 | |||
| 715f4af179 | |||
| 3bd0fa8b32 | |||
| c295ef085f | |||
| c1ad3c8057 | |||
| af92e32822 | |||
| 2954e7ea66 | |||
| 8eefee56ff | |||
| fd93073146 | |||
| a9a8e40321 | |||
| e66aed5b99 | |||
| 7d4d170ce4 | |||
| eb860c0899 | |||
| b6d3de7123 | |||
| f058970169 | |||
| 77825f8d35 | |||
| 3789a30955 |
@@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
+1
-2
@@ -4,7 +4,6 @@
|
|||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
sauce_connect.log*
|
|
||||||
.idea
|
.idea
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
node_modules
|
node_modules
|
||||||
@@ -15,7 +14,7 @@ node_modules
|
|||||||
lib/handlebars/compiler/parser.js
|
lib/handlebars/compiler/parser.js
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/integration-testing/*/dist/
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
# Third-party or files that must remain unchanged
|
# Third-party or files that must remain unchanged
|
||||||
/spec/expected/
|
/spec/expected/
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/"
|
||||||
|
open-pull-requests-limit: 0
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
allow:
|
||||||
|
- dependency-type: production
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test (Node)
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||||
|
# https://nodejs.org/en/about/releases/
|
||||||
|
node-version: ['16', '18', '20', '22']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
|
- name: Test (Integration)
|
||||||
|
# https://github.com/webpack/webpack/issues/14532
|
||||||
|
if: ${{ matrix.node-version == '16' }}
|
||||||
|
run: |
|
||||||
|
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-test && ./test.sh && cd -
|
||||||
|
|
||||||
|
browser:
|
||||||
|
name: Test (Browser)
|
||||||
|
runs-on: 'ubuntu-22.04'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install Playwright
|
||||||
|
run: |
|
||||||
|
npx playwright install-deps
|
||||||
|
npx playwright install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npx grunt prepare
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test:browser
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-aws-s3:
|
||||||
|
name: Publish to AWS S3
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: |
|
||||||
|
git config --global user.email "release@handlebarsjs.com"
|
||||||
|
git config --global user.name "handlebars-lang"
|
||||||
|
npm run publish:aws
|
||||||
|
env:
|
||||||
|
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||||
|
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||||
+3
-3
@@ -4,7 +4,6 @@
|
|||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
sauce_connect.log*
|
|
||||||
.idea
|
.idea
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
@@ -16,5 +15,6 @@ node_modules
|
|||||||
lib/handlebars/compiler/parser.js
|
lib/handlebars/compiler/parser.js
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/integration-testing/*/dist/
|
/test-results/
|
||||||
/spec/tmp/*
|
/tests/integration/*/dist/
|
||||||
|
/spec/tmp/*
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "spec/mustache"]
|
[submodule "spec/mustache"]
|
||||||
path = spec/mustache
|
path = spec/mustache
|
||||||
url = git://github.com/mustache/spec.git
|
url = https://github.com/mustache/spec.git
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ node_modules
|
|||||||
lib/handlebars/compiler/parser.js
|
lib/handlebars/compiler/parser.js
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/integration-testing/*/dist/
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
# Third-party or files that must remain unchanged
|
# Third-party or files that must remain unchanged
|
||||||
/spec/expected/
|
/spec/expected/
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: test
|
|
||||||
name: check javascript (eslint)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run lint
|
|
||||||
- stage: test
|
|
||||||
name: check formatting (prettier)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run check-format
|
|
||||||
- stage: test
|
|
||||||
name: check typescript definitions (dtslint)
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run dtslint
|
|
||||||
- stage: test
|
|
||||||
name: extensive tests and publish to aws
|
|
||||||
script: npm run extensive-tests-and-publish-to-aws
|
|
||||||
env:
|
|
||||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
|
||||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
|
||||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
|
||||||
- SAUCE_USERNAME=handlebars
|
|
||||||
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
|
|
||||||
node_js: '10'
|
|
||||||
- stage: test
|
|
||||||
name: test with latest nodejs-lts
|
|
||||||
node_js: lts/*
|
|
||||||
script: npm run test
|
|
||||||
- stage: test
|
|
||||||
name: test with active nodejs
|
|
||||||
node_js: node
|
|
||||||
script: npm run test
|
|
||||||
cache: npm
|
|
||||||
email:
|
|
||||||
on_failure: change
|
|
||||||
on_success: never
|
|
||||||
git:
|
|
||||||
depth: 100
|
|
||||||
+96
-28
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||||
|
|
||||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||||
|
|
||||||
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||||
|
|
||||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ You can also run our set of benchmarks with `grunt bench`.
|
|||||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||||
|
|
||||||
If you notice any problems, please report them to the GitHub issue tracker at
|
If you notice any problems, please report them to the GitHub issue tracker at
|
||||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||||
|
|
||||||
##Running Tests
|
## Running Tests
|
||||||
|
|
||||||
To run tests locally, first install all dependencies.
|
To run tests locally, first install all dependencies.
|
||||||
|
|
||||||
@@ -78,43 +78,111 @@ We do linting and formatting in two phases:
|
|||||||
|
|
||||||
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||||
while warnings are allowed.
|
while warnings are allowed.
|
||||||
- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||||
are forbidden.
|
are forbidden.
|
||||||
|
|
||||||
You can use the following scripts to make sure that the travis-job does not fail:
|
You can use the following scripts to make sure that the CI job does not fail:
|
||||||
|
|
||||||
- **npm run lint** will run `eslint` and fail on warnings
|
- **npm run lint** will run `eslint` and fail on warnings
|
||||||
- **npm run format** will run `prettier` on all files
|
- **npm run format** will run `prettier` on all files
|
||||||
- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
|
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||||
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||||
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||||
|
|
||||||
## Releasing the latest version
|
## Releasing the latest version
|
||||||
|
|
||||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||||
|
|
||||||
- Push-access to `wycats/handlebars.js`
|
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
|
||||||
- Publishing rights on npmjs.com for the `handlebars` package
|
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
|
||||||
- Publishing rights on gemfury for the `handlebars-source` package
|
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
|
||||||
- Push-access to the repo for legacy package managers: `components/handlebars`
|
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
|
||||||
- Push-access to the production-repo of the handlebars site: `handlebars-lang/handlebarsjs.com-github-pages`
|
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
|
||||||
|
|
||||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||||
|
|
||||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
A full release via Docker may be completed with the following:
|
||||||
|
|
||||||
A full release may be completed with the following:
|
1. Create a `Dockerfile` in this folder for releasing
|
||||||
|
```Dockerfile
|
||||||
|
FROM node:10-slim
|
||||||
|
|
||||||
|
ENV EDITOR=vim
|
||||||
|
|
||||||
|
# Update stretch repositories
|
||||||
|
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
|
||||||
|
-e 's|security.debian.org|archive.debian.org/|g' \
|
||||||
|
-e '/stretch-updates/d' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Install release dependencies
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y git vim
|
||||||
|
|
||||||
|
# Work around deprecated npm dependency install via unauthenticated git-protocol:
|
||||||
|
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
|
||||||
|
RUN git config --system url."https://github.com/".insteadOf git://github.com/
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
RUN git config --system user.email "release@handlebarsjs.com"
|
||||||
|
RUN git config --system user.name "handlebars-lang"
|
||||||
|
|
||||||
|
RUN mkdir /home/node/.config
|
||||||
|
RUN mkdir /home/node/.ssh
|
||||||
|
RUN mkdir /home/node/tmp
|
||||||
|
|
||||||
|
# Generate config for yo generator-release:
|
||||||
|
# https://github.com/kpdecker/generator-release#example
|
||||||
|
# You have to add a valid GitHub access token! (Used for reading issues and pull requests.)
|
||||||
|
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub personal access token'\n};" > /home/node/.config/generator-release
|
||||||
|
RUN chown -R node:node /home/node/.config
|
||||||
|
RUN chown -R node:node /home/node/.ssh
|
||||||
|
RUN chown -R node:node /home/node/tmp
|
||||||
|
|
||||||
|
# Add the generated key to GitHub: https://github.com/settings/keys
|
||||||
|
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
|
||||||
|
RUN chmod 0600 /home/node/.ssh/id_ed25519*
|
||||||
|
RUN chown node:node /home/node/.ssh/id_ed25519*
|
||||||
|
```
|
||||||
|
2. Build and run the Docker image
|
||||||
|
```bash
|
||||||
|
docker build --tag handlebars:release .
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
|
||||||
|
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
|
||||||
|
```
|
||||||
|
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
|
||||||
|
* Add GitHub API token: `vi /home/node/.config/generator-release`
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm install -g yo@1 grunt@1 generator-release
|
||||||
|
npm run release
|
||||||
|
# Warning! This step will collect data from GitHub, bump the version,
|
||||||
|
# create a new commit, create a new tag and push it to GitHub.
|
||||||
|
# https://github.com/kpdecker/generator-release?tab=readme-ov-file#usage
|
||||||
|
yo release
|
||||||
|
npm login
|
||||||
|
npm publish
|
||||||
|
yo release:publish components handlebars.js dist/components/
|
||||||
|
```
|
||||||
|
6. Publish Ruby `handlebars-source` gem:
|
||||||
|
```bash
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
ruby:3.2-slim bash
|
||||||
|
```
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
cd dist/components/
|
||||||
|
gem build handlebars-source.gemspec
|
||||||
|
gem push handlebars-source-*.gem
|
||||||
|
```
|
||||||
|
|
||||||
```
|
### After the release
|
||||||
npm ci
|
|
||||||
yo release
|
|
||||||
npm publish
|
|
||||||
yo release:publish components handlebars.js dist/components/
|
|
||||||
|
|
||||||
cd dist/components/
|
|
||||||
gem build handlebars-source.gemspec
|
|
||||||
gem push handlebars-source-*.gem
|
|
||||||
```
|
|
||||||
|
|
||||||
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
||||||
in those places still point to the latest version
|
in those places still point to the latest version
|
||||||
@@ -126,13 +194,13 @@ in those places still point to the latest version
|
|||||||
|
|
||||||
When everything is OK, the **handlebars site** needs to be updated.
|
When everything is OK, the **handlebars site** needs to be updated.
|
||||||
|
|
||||||
Go to the master branch of the repo [handlebars-lang/handlebarsjs.com-github-pages](https://github.com/handlebars-lang/handlebarsjs.com-github-pages/tree/master)
|
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
|
||||||
and make a minimal change to the README. This will invoke a github-action that redeploys
|
and make a minimal change to the README. This will invoke a github-action that redeploys
|
||||||
the site, fetching the latest version-number from the npm-registry.
|
the site, fetching the latest version-number from the npm-registry.
|
||||||
(note that the default-branch of this repo is not the master and regular changes are done
|
(note that the default-branch of this repo is not the master and regular changes are done
|
||||||
in the `handlebars-lang/docs`-repo).
|
in the `handlebars-lang/docs`-repo).
|
||||||
|
|
||||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||||
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
1. How can I file a bug report:
|
1. How can I file a bug report:
|
||||||
|
|
||||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why isn't my Mustache template working?
|
1. Why isn't my Mustache template working?
|
||||||
|
|
||||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||||
|
|
||||||
1. Why is it slower when compiling?
|
1. Why is it slower when compiling?
|
||||||
|
|
||||||
@@ -36,16 +36,18 @@
|
|||||||
```sh
|
```sh
|
||||||
handlebars --version
|
handlebars --version
|
||||||
```
|
```
|
||||||
|
|
||||||
If using the integrated precompiler and
|
If using the integrated precompiler and
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
console.log(Handlebars.VERSION);
|
console.log(Handlebars.VERSION);
|
||||||
```
|
```
|
||||||
|
|
||||||
On the client side.
|
On the client side.
|
||||||
|
|
||||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||||
|
|
||||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why doesn't IE like the `default` name in the AMD module?
|
1. Why doesn't IE like the `default` name in the AMD module?
|
||||||
|
|
||||||
@@ -53,8 +55,8 @@
|
|||||||
|
|
||||||
1. How do I load the runtime library when using AMD?
|
1. How do I load the runtime library when using AMD?
|
||||||
|
|
||||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/handlebars-lang/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||||
|
|
||||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||||
|
|
||||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||||
|
|||||||
+21
-82
@@ -7,7 +7,7 @@ module.exports = function(grunt) {
|
|||||||
'tmp',
|
'tmp',
|
||||||
'dist',
|
'dist',
|
||||||
'lib/handlebars/compiler/parser.js',
|
'lib/handlebars/compiler/parser.js',
|
||||||
'integration-testing/**/node_modules'
|
'/tests/integration/**/node_modules'
|
||||||
],
|
],
|
||||||
|
|
||||||
copy: {
|
copy: {
|
||||||
@@ -165,54 +165,10 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'saucelabs-mocha': {
|
|
||||||
all: {
|
|
||||||
options: {
|
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
|
||||||
urls: [
|
|
||||||
'http://localhost:9999/spec/?headless=true',
|
|
||||||
'http://localhost:9999/spec/amd.html?headless=true'
|
|
||||||
],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 4,
|
|
||||||
browsers: [
|
|
||||||
{ browserName: 'chrome' },
|
|
||||||
{ browserName: 'firefox', platform: 'Linux' },
|
|
||||||
// {browserName: '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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sanity: {
|
|
||||||
options: {
|
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
|
||||||
urls: [
|
|
||||||
'http://localhost:9999/spec/umd.html?headless=true',
|
|
||||||
'http://localhost:9999/spec/amd-runtime.html?headless=true',
|
|
||||||
'http://localhost:9999/spec/umd-runtime.html?headless=true'
|
|
||||||
],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 2,
|
|
||||||
browsers: [{ browserName: 'chrome' }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bgShell: {
|
shell: {
|
||||||
integrationTests: {
|
integrationTests: {
|
||||||
cmd: './integration-testing/run-integration-tests.sh',
|
command: './tests/integration/run-integration-tests.sh'
|
||||||
bg: false,
|
|
||||||
fail: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -237,23 +193,15 @@ module.exports = function(grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-babel');
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
grunt.loadNpmTasks('grunt-bg-shell');
|
grunt.loadNpmTasks('grunt-shell');
|
||||||
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
|
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
grunt.task.loadTasks('tasks');
|
||||||
|
|
||||||
this.registerTask(
|
grunt.registerTask('node', ['babel:cjs']);
|
||||||
'build',
|
grunt.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||||
'Builds a distributable version of the current project',
|
grunt.registerTask('globals', ['webpack']);
|
||||||
['parser', 'node', 'globals']
|
grunt.registerTask('release', 'Build final packages', [
|
||||||
);
|
|
||||||
|
|
||||||
this.registerTask('node', ['babel:cjs']);
|
|
||||||
this.registerTask('globals', ['webpack']);
|
|
||||||
|
|
||||||
this.registerTask('release', 'Build final packages', [
|
|
||||||
'amd',
|
|
||||||
'uglify',
|
'uglify',
|
||||||
'test:min',
|
'test:min',
|
||||||
'copy:dist',
|
'copy:dist',
|
||||||
@@ -261,38 +209,29 @@ module.exports = function(grunt) {
|
|||||||
'copy:cdnjs'
|
'copy:cdnjs'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
// Requires secret properties from .travis.yaml
|
||||||
|
|
||||||
this.registerTask('test', ['test:bin', 'test:cov']);
|
|
||||||
|
|
||||||
grunt.registerTask('bench', ['metrics']);
|
|
||||||
|
|
||||||
if (process.env.SAUCE_ACCESS_KEY) {
|
|
||||||
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
|
|
||||||
} else {
|
|
||||||
grunt.registerTask('sauce', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
|
|
||||||
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
||||||
'default',
|
'default',
|
||||||
'bgShell:integrationTests',
|
'shell:integrationTests',
|
||||||
'sauce',
|
|
||||||
'metrics',
|
'metrics',
|
||||||
'publish-to-aws'
|
'publish-to-aws'
|
||||||
]);
|
]);
|
||||||
grunt.registerTask('on-file-change', [
|
|
||||||
'build',
|
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
|
||||||
'amd',
|
|
||||||
'concat:tests',
|
|
||||||
'test'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// === Primary tasks ===
|
// === Primary tasks ===
|
||||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||||
|
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||||
|
grunt.registerTask('bench', ['metrics']);
|
||||||
|
grunt.registerTask('prepare', ['build', 'concat:tests']);
|
||||||
|
grunt.registerTask(
|
||||||
|
'build',
|
||||||
|
'Builds a distributable version of the current project',
|
||||||
|
['parser', 'node', 'amd', 'globals']
|
||||||
|
);
|
||||||
grunt.registerTask('integration-tests', [
|
grunt.registerTask('integration-tests', [
|
||||||
'default',
|
'default',
|
||||||
'bgShell:integrationTests'
|
'shell:integrationTests'
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-16
@@ -1,17 +1,18 @@
|
|||||||
[](https://travis-ci.org/wycats/handlebars.js)
|
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||||
[](https://ci.appveyor.com/project/wycats/handlebars-js)
|
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||||
[](https://saucelabs.com/u/handlebars)
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://bundlephobia.com/package/handlebars)
|
||||||
|
[](https://packagephobia.com/result?p=handlebars)
|
||||||
|
|
||||||
Handlebars.js
|
Handlebars.js
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Handlebars.js is an extension to the [Mustache templating
|
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||||
Handlebars.js and Mustache are both logicless templating languages that
|
|
||||||
keep the view and the code separated like we all know they should be.
|
|
||||||
|
|
||||||
Checkout the official Handlebars docs site at
|
Checkout the official Handlebars docs site at
|
||||||
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
@@ -22,7 +23,7 @@ Usage
|
|||||||
-----
|
-----
|
||||||
In general, the syntax of Handlebars.js templates is a superset
|
In general, the syntax of Handlebars.js templates is a superset
|
||||||
of Mustache templates. For basic syntax, check out the [Mustache
|
of Mustache templates. For basic syntax, check out the [Mustache
|
||||||
manpage](http://mustache.github.com/mustache.5.html).
|
manpage](https://mustache.github.io/mustache.5.html).
|
||||||
|
|
||||||
Once you have a template, use the `Handlebars.compile` method to compile
|
Once you have a template, use the `Handlebars.compile` method to compile
|
||||||
the template into a function. The generated function takes a context
|
the template into a function. The generated function takes a context
|
||||||
@@ -64,7 +65,7 @@ templates easier and also changes a tiny detail of how partials work.
|
|||||||
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
||||||
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
||||||
|
|
||||||
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
||||||
@@ -89,8 +90,6 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
|
|||||||
Older versions and other runtimes are likely to work but have not been formally
|
Older versions and other runtimes are likely to work but have not been formally
|
||||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
||||||
|
|
||||||
[](https://saucelabs.com/u/handlebars)
|
|
||||||
|
|
||||||
Performance
|
Performance
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -102,18 +101,18 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
|||||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||||
rewritten Handlebars (current version) is faster than the old version,
|
rewritten Handlebars (current version) is faster than the old version,
|
||||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||||
|
|
||||||
|
|
||||||
Upgrading
|
Upgrading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
------------
|
------------
|
||||||
|
|
||||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||||
|
|
||||||
|
|
||||||
Handlebars in the Wild
|
Handlebars in the Wild
|
||||||
@@ -165,4 +164,4 @@ License
|
|||||||
-------
|
-------
|
||||||
Handlebars.js is released under the MIT license.
|
Handlebars.js is released under the MIT license.
|
||||||
|
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
# Test against these versions of Node.js
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- nodejs_version: "10"
|
|
||||||
|
|
||||||
platform:
|
|
||||||
- x64
|
|
||||||
|
|
||||||
# Install scripts (runs after repo cloning)
|
|
||||||
install:
|
|
||||||
# Get the latest stable version of Node.js
|
|
||||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
|
||||||
# Clone submodules (mustache spec)
|
|
||||||
- cmd: git submodule update --init --recursive
|
|
||||||
# Install modules
|
|
||||||
- cmd: npm ci
|
|
||||||
|
|
||||||
|
|
||||||
# Post-install test scripts
|
|
||||||
test_script:
|
|
||||||
# Output useful info for debugging
|
|
||||||
- cmd: node --version
|
|
||||||
- cmd: npm --version
|
|
||||||
# Run tests
|
|
||||||
- cmd: npm run test
|
|
||||||
|
|
||||||
# Don't actually build
|
|
||||||
build: off
|
|
||||||
|
|
||||||
on_failure:
|
|
||||||
- cmd: 7z a coverage.zip coverage
|
|
||||||
- cmd: appveyor PushArtifact coverage.zip
|
|
||||||
|
|
||||||
|
|
||||||
# Set build version format here instead of in the admin panel
|
|
||||||
version: "{build}"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "prettier",
|
|
||||||
"globals": {
|
|
||||||
"require": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// Disabling for tests, for now.
|
|
||||||
"no-path-concat": 0,
|
|
||||||
|
|
||||||
"no-var": 0,
|
|
||||||
"no-shadow": 0,
|
|
||||||
"handle-callback-err": 0,
|
|
||||||
"no-console": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.6",
|
"version": "4.7.9",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "components/handlebars.js",
|
"name": "components/handlebars.js",
|
||||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||||
"homepage": "http://handlebarsjs.com",
|
"homepage": "https://handlebarsjs.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "component",
|
"type": "component",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -11,13 +11,9 @@
|
|||||||
],
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Chris Wanstrath",
|
"name": "Chris Wanstrath"
|
||||||
"homepage": "http://chriswanstrath.com"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
|
||||||
"robloach/component-installer": "*"
|
|
||||||
},
|
|
||||||
"extra": {
|
"extra": {
|
||||||
"component": {
|
"component": {
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|||||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||||
gem.summary = %q{Handlebars.js source code wrapper}
|
gem.summary = %q{Handlebars.js source code wrapper}
|
||||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||||
gem.version = package["version"].sub "-", "."
|
gem.version = package["version"].sub "-", "."
|
||||||
gem.license = "MIT"
|
gem.license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>4.7.6</version>
|
<version>4.7.9</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Extension of the Mustache logicless template language</description>
|
<description>Extension of the Mustache logicless template language</description>
|
||||||
<releaseNotes></releaseNotes>
|
<releaseNotes></releaseNotes>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.6",
|
"version": "4.7.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ Decorators are executed when the block program is instantiated and are passed `(
|
|||||||
|
|
||||||
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||||
|
|
||||||
The [inline partial](https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "webpack-test",
|
|
||||||
"description": "Various tests with Handlebars and Webpack",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"test": "node dist/main.js"
|
|
||||||
},
|
|
||||||
"private": true,
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"handlebars": "file:../..",
|
|
||||||
"handlebars-loader": "^1.7.1",
|
|
||||||
"webpack": "^4.39.3",
|
|
||||||
"webpack-cli": "^3.3.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
// Handlebars should not use node or browser-specific APIs
|
||||||
|
'shared-node-browser': true,
|
||||||
|
node: false,
|
||||||
|
browser: false
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@ import { registerDefaultDecorators } from './decorators';
|
|||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { resetLoggedProperties } from './internal/proto-access';
|
import { resetLoggedProperties } from './internal/proto-access';
|
||||||
|
|
||||||
export const VERSION = '4.7.6';
|
export const VERSION = '4.7.9';
|
||||||
export const COMPILER_REVISION = 8;
|
export const COMPILER_REVISION = 8;
|
||||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global define */
|
/* global define, require */
|
||||||
import { isArray } from '../utils';
|
import { isArray } from '../utils';
|
||||||
|
|
||||||
let SourceNode;
|
let SourceNode;
|
||||||
@@ -6,7 +6,7 @@ let SourceNode;
|
|||||||
try {
|
try {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (typeof define !== 'function' || !define.amd) {
|
if (typeof define !== 'function' || !define.amd) {
|
||||||
// We don't support this in AMD environments. For these environments, we asusme that
|
// We don't support this in AMD environments. For these environments, we assume that
|
||||||
// they are running on the browser and thus have no need for the source-map library.
|
// they are running on the browser and thus have no need for the source-map library.
|
||||||
let SourceMap = require('source-map');
|
let SourceMap = require('source-map');
|
||||||
SourceNode = SourceMap.SourceNode;
|
SourceNode = SourceMap.SourceNode;
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
/* eslint-disable new-cap */
|
/* eslint-disable new-cap */
|
||||||
|
|
||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
import { isArray, indexOf, extend } from '../utils';
|
import {
|
||||||
|
isArray,
|
||||||
|
indexOf,
|
||||||
|
extend,
|
||||||
|
sanitizeDepth,
|
||||||
|
sanitizeParts
|
||||||
|
} from '../utils';
|
||||||
import AST from './ast';
|
import AST from './ast';
|
||||||
|
|
||||||
const slice = [].slice;
|
const slice = [].slice;
|
||||||
@@ -243,7 +249,7 @@ Compiler.prototype = {
|
|||||||
name = path.parts[0],
|
name = path.parts[0],
|
||||||
isBlock = program != null || inverse != null;
|
isBlock = program != null || inverse != null;
|
||||||
|
|
||||||
this.opcode('getContext', path.depth);
|
this.opcode('getContext', sanitizeDepth(path.depth));
|
||||||
|
|
||||||
this.opcode('pushProgram', program);
|
this.opcode('pushProgram', program);
|
||||||
this.opcode('pushProgram', inverse);
|
this.opcode('pushProgram', inverse);
|
||||||
@@ -288,29 +294,32 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
PathExpression: function(path) {
|
PathExpression: function(path) {
|
||||||
this.addDepth(path.depth);
|
// Sanitize untrusted AST values at the compiler boundary.
|
||||||
this.opcode('getContext', path.depth);
|
// javascript-compiler.js trusts all opcode arguments to be safe.
|
||||||
|
const depth = sanitizeDepth(path.depth);
|
||||||
|
const parts = sanitizeParts(path.parts);
|
||||||
|
|
||||||
let name = path.parts[0],
|
this.addDepth(depth);
|
||||||
|
this.opcode('getContext', depth);
|
||||||
|
|
||||||
|
let name = parts[0],
|
||||||
scoped = AST.helpers.scopedId(path),
|
scoped = AST.helpers.scopedId(path),
|
||||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
blockParamId = !depth && !scoped && this.blockParamIndex(name);
|
||||||
|
|
||||||
if (blockParamId) {
|
if (blockParamId) {
|
||||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
this.opcode(
|
||||||
|
'lookupBlockParam',
|
||||||
|
[Number(blockParamId[0]), Number(blockParamId[1])],
|
||||||
|
parts
|
||||||
|
);
|
||||||
} else if (!name) {
|
} else if (!name) {
|
||||||
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
||||||
this.opcode('pushContext');
|
this.opcode('pushContext');
|
||||||
} else if (path.data) {
|
} else if (path.data) {
|
||||||
this.options.data = true;
|
this.options.data = true;
|
||||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
this.opcode('lookupData', depth, parts, path.strict);
|
||||||
} else {
|
} else {
|
||||||
this.opcode(
|
this.opcode('lookupOnContext', parts, path.falsy, path.strict, scoped);
|
||||||
'lookupOnContext',
|
|
||||||
path.parts,
|
|
||||||
path.falsy,
|
|
||||||
path.strict,
|
|
||||||
scoped
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -319,11 +328,11 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
NumberLiteral: function(number) {
|
NumberLiteral: function(number) {
|
||||||
this.opcode('pushLiteral', number.value);
|
this.opcode('pushLiteral', Number(number.value));
|
||||||
},
|
},
|
||||||
|
|
||||||
BooleanLiteral: function(bool) {
|
BooleanLiteral: function(bool) {
|
||||||
this.opcode('pushLiteral', bool.value);
|
this.opcode('pushLiteral', bool.value === true ? 'true' : 'false');
|
||||||
},
|
},
|
||||||
|
|
||||||
UndefinedLiteral: function() {
|
UndefinedLiteral: function() {
|
||||||
@@ -410,16 +419,16 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
pushParam: function(val) {
|
pushParam: function(val) {
|
||||||
let value = val.value != null ? val.value : val.original || '';
|
let value = val.value != null ? val.value : val.original || '';
|
||||||
|
let depth = sanitizeDepth(val.depth);
|
||||||
|
|
||||||
if (this.stringParams) {
|
if (this.stringParams) {
|
||||||
if (value.replace) {
|
if (value.replace) {
|
||||||
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||||
}
|
}
|
||||||
|
if (depth) {
|
||||||
if (val.depth) {
|
this.addDepth(depth);
|
||||||
this.addDepth(val.depth);
|
|
||||||
}
|
}
|
||||||
this.opcode('getContext', val.depth || 0);
|
this.opcode('getContext', depth);
|
||||||
this.opcode('pushStringParam', value, val.type);
|
this.opcode('pushStringParam', value, val.type);
|
||||||
|
|
||||||
if (val.type === 'SubExpression') {
|
if (val.type === 'SubExpression') {
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ JavaScriptCompiler.prototype = {
|
|||||||
return this.internalNameLookup(parent, name);
|
return this.internalNameLookup(parent, name);
|
||||||
},
|
},
|
||||||
depthedLookup: function(name) {
|
depthedLookup: function(name) {
|
||||||
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
|
return [
|
||||||
|
this.aliasable('container.lookup'),
|
||||||
|
'(depths, ',
|
||||||
|
JSON.stringify(name),
|
||||||
|
')'
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
compilerInfo: function() {
|
compilerInfo: function() {
|
||||||
@@ -160,12 +165,10 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
let { programs, decorators } = this.context;
|
let { programs, decorators } = this.context;
|
||||||
for (i = 0, l = programs.length; i < l; i++) {
|
for (i = 0, l = programs.length; i < l; i++) {
|
||||||
if (programs[i]) {
|
ret[i] = programs[i];
|
||||||
ret[i] = programs[i];
|
if (decorators[i]) {
|
||||||
if (decorators[i]) {
|
ret[i + '_d'] = decorators[i];
|
||||||
ret[i + '_d'] = decorators[i];
|
ret.useDecorators = true;
|
||||||
ret.useDecorators = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,14 +533,22 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.resolvePath('data', parts, 0, true, strict);
|
this.resolvePath('data', parts, 0, true, strict);
|
||||||
},
|
},
|
||||||
|
|
||||||
resolvePath: function(type, parts, i, falsy, strict) {
|
resolvePath: function(type, parts, startPartIndex, falsy, strict) {
|
||||||
if (this.options.strict || this.options.assumeObjects) {
|
if (this.options.strict || this.options.assumeObjects) {
|
||||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
this.push(
|
||||||
|
strictLookup(
|
||||||
|
this.options.strict && strict,
|
||||||
|
this,
|
||||||
|
parts,
|
||||||
|
startPartIndex,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = parts.length;
|
let len = parts.length;
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
this.replaceStack(current => {
|
this.replaceStack(current => {
|
||||||
let lookup = this.nameLookup(current, parts[i], type);
|
let lookup = this.nameLookup(current, parts[i], type);
|
||||||
@@ -675,9 +686,18 @@ JavaScriptCompiler.prototype = {
|
|||||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||||
options = this.setupHelperArgs(name, paramSize);
|
options = this.setupHelperArgs(name, paramSize);
|
||||||
|
|
||||||
|
// Store the resolved decorator in a variable and verify it is a function before
|
||||||
|
// calling it. Without this, unregistered decorators can cause an unhandled TypeError
|
||||||
|
// (calling undefined), which crashes the process — enabling Denial of Service.
|
||||||
|
this.decorators.push(['var decorator = ', foundDecorator, ';']);
|
||||||
|
this.decorators.push([
|
||||||
|
'if (typeof decorator !== "function") { throw new Error(',
|
||||||
|
this.quotedString('Missing decorator: "' + name + '"'),
|
||||||
|
'); }'
|
||||||
|
]);
|
||||||
this.decorators.push([
|
this.decorators.push([
|
||||||
'fn = ',
|
'fn = ',
|
||||||
this.decorators.functionCall(foundDecorator, '', [
|
this.decorators.functionCall('decorator', '', [
|
||||||
'fn',
|
'fn',
|
||||||
'props',
|
'props',
|
||||||
'container',
|
'container',
|
||||||
@@ -901,8 +921,8 @@ JavaScriptCompiler.prototype = {
|
|||||||
let existing = this.matchExistingProgram(child);
|
let existing = this.matchExistingProgram(child);
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
// Placeholder to prevent name conflicts for nested children
|
||||||
let index = this.context.programs.length;
|
let index = this.context.programs.push('') - 1;
|
||||||
child.index = index;
|
child.index = index;
|
||||||
child.name = 'program' + index;
|
child.name = 'program' + index;
|
||||||
this.context.programs[index] = compiler.compile(
|
this.context.programs[index] = compiler.compile(
|
||||||
@@ -1256,15 +1276,14 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
||||||
let stack = compiler.popStack(),
|
let stack = compiler.popStack(),
|
||||||
i = 0,
|
|
||||||
len = parts.length;
|
len = parts.length;
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
stack = compiler.nameLookup(stack, parts[i], type);
|
stack = compiler.nameLookup(stack, parts[i], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1274,7 +1293,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
|||||||
'(',
|
'(',
|
||||||
stack,
|
stack,
|
||||||
', ',
|
', ',
|
||||||
compiler.quotedString(parts[i]),
|
compiler.quotedString(parts[len]),
|
||||||
', ',
|
', ',
|
||||||
JSON.stringify(compiler.source.currentLocation),
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
' )'
|
' )'
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export function moveHelperToHooks(instance, helperName, keepHelper) {
|
|||||||
if (instance.helpers[helperName]) {
|
if (instance.helpers[helperName]) {
|
||||||
instance.hooks[helperName] = instance.helpers[helperName];
|
instance.hooks[helperName] = instance.helpers[helperName];
|
||||||
if (!keepHelper) {
|
if (!keepHelper) {
|
||||||
delete instance.helpers[helperName];
|
// Using delete is slow
|
||||||
|
instance.helpers[helperName] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ export default function(instance) {
|
|||||||
execIteration(i, i, i === context.length - 1);
|
execIteration(i, i, i === context.length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (global.Symbol && context[global.Symbol.iterator]) {
|
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||||
const newContext = [];
|
const newContext = [];
|
||||||
const iterator = context[global.Symbol.iterator]();
|
const iterator = context[Symbol.iterator]();
|
||||||
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||||
newContext.push(it.value);
|
newContext.push(it.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { extend } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
|
|
||||||
* The resulting object can be used with "object[property]" to check if a property exists
|
|
||||||
* @param {...object} sources a varargs parameter of source objects that will be merged
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createNewLookupObject(...sources) {
|
|
||||||
return extend(Object.create(null), ...sources);
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,31 @@
|
|||||||
import { createNewLookupObject } from './create-new-lookup-object';
|
import { extend } from '../utils';
|
||||||
import * as logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
const loggedProperties = Object.create(null);
|
const loggedProperties = Object.create(null);
|
||||||
|
|
||||||
export function createProtoAccessControl(runtimeOptions) {
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
let defaultMethodWhiteList = Object.create(null);
|
// Create an object with "null"-prototype to avoid truthy results on
|
||||||
defaultMethodWhiteList['constructor'] = false;
|
// prototype properties.
|
||||||
defaultMethodWhiteList['__defineGetter__'] = false;
|
const propertyWhiteList = Object.create(null);
|
||||||
defaultMethodWhiteList['__defineSetter__'] = false;
|
|
||||||
defaultMethodWhiteList['__lookupGetter__'] = false;
|
|
||||||
|
|
||||||
let defaultPropertyWhiteList = Object.create(null);
|
|
||||||
// eslint-disable-next-line no-proto
|
// eslint-disable-next-line no-proto
|
||||||
defaultPropertyWhiteList['__proto__'] = false;
|
propertyWhiteList['__proto__'] = false;
|
||||||
|
extend(propertyWhiteList, runtimeOptions.allowedProtoProperties);
|
||||||
|
|
||||||
|
const methodWhiteList = Object.create(null);
|
||||||
|
methodWhiteList['constructor'] = false;
|
||||||
|
methodWhiteList['__defineGetter__'] = false;
|
||||||
|
methodWhiteList['__defineSetter__'] = false;
|
||||||
|
methodWhiteList['__lookupGetter__'] = false;
|
||||||
|
methodWhiteList['__lookupSetter__'] = false;
|
||||||
|
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
properties: {
|
properties: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: propertyWhiteList,
|
||||||
defaultPropertyWhiteList,
|
|
||||||
runtimeOptions.allowedProtoProperties
|
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: methodWhiteList,
|
||||||
defaultMethodWhiteList,
|
|
||||||
runtimeOptions.allowedProtoMethods
|
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
/* global globalThis */
|
||||||
export default function(Handlebars) {
|
export default function(Handlebars) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
let root = typeof global !== 'undefined' ? global : window,
|
// https://mathiasbynens.be/notes/globalthis
|
||||||
$Handlebars = root.Handlebars;
|
(function() {
|
||||||
|
if (typeof globalThis === 'object') return;
|
||||||
|
Object.prototype.__defineGetter__('__magic__', function() {
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
__magic__.globalThis = __magic__; // eslint-disable-line no-undef
|
||||||
|
delete Object.prototype.__magic__;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const $Handlebars = globalThis.Handlebars;
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Handlebars.noConflict = function() {
|
Handlebars.noConflict = function() {
|
||||||
if (root.Handlebars === Handlebars) {
|
if (globalThis.Handlebars === Handlebars) {
|
||||||
root.Handlebars = $Handlebars;
|
globalThis.Handlebars = $Handlebars;
|
||||||
}
|
}
|
||||||
return Handlebars;
|
return Handlebars;
|
||||||
};
|
};
|
||||||
|
|||||||
+25
-23
@@ -74,17 +74,10 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
|
|
||||||
let extendedOptions = Utils.extend({}, options, {
|
options.hooks = this.hooks;
|
||||||
hooks: this.hooks,
|
options.protoAccessControl = this.protoAccessControl;
|
||||||
protoAccessControl: this.protoAccessControl
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = env.VM.invokePartial.call(
|
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||||
this,
|
|
||||||
partial,
|
|
||||||
context,
|
|
||||||
extendedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null && env.compile) {
|
if (result == null && env.compile) {
|
||||||
options.partials[options.name] = env.compile(
|
options.partials[options.name] = env.compile(
|
||||||
@@ -92,7 +85,7 @@ export function template(templateSpec, env) {
|
|||||||
templateSpec.compilerOptions,
|
templateSpec.compilerOptions,
|
||||||
env
|
env
|
||||||
);
|
);
|
||||||
result = options.partials[options.name](context, extendedOptions);
|
result = options.partials[options.name](context, options);
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (options.indent) {
|
if (options.indent) {
|
||||||
@@ -124,7 +117,7 @@ export function template(templateSpec, env) {
|
|||||||
loc: loc
|
loc: loc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return obj[name];
|
return container.lookupProperty(obj, name);
|
||||||
},
|
},
|
||||||
lookupProperty: function(parent, propertyName) {
|
lookupProperty: function(parent, propertyName) {
|
||||||
let result = parent[propertyName];
|
let result = parent[propertyName];
|
||||||
@@ -145,7 +138,7 @@ export function template(templateSpec, env) {
|
|||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return depths[i][name];
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -254,8 +247,9 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
let mergedHelpers = {};
|
||||||
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
addHelpers(mergedHelpers, env.helpers, container);
|
||||||
|
addHelpers(mergedHelpers, options.helpers, container);
|
||||||
container.helpers = mergedHelpers;
|
container.helpers = mergedHelpers;
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
@@ -355,21 +349,21 @@ export function wrapProgram(
|
|||||||
export function resolvePartial(partial, context, options) {
|
export function resolvePartial(partial, context, options) {
|
||||||
if (!partial) {
|
if (!partial) {
|
||||||
if (options.name === '@partial-block') {
|
if (options.name === '@partial-block') {
|
||||||
partial = options.data['partial-block'];
|
partial = lookupOwnProperty(options.data, 'partial-block');
|
||||||
} else {
|
} else {
|
||||||
partial = options.partials[options.name];
|
partial = lookupOwnProperty(options.partials, options.name);
|
||||||
}
|
}
|
||||||
} else if (!partial.call && !options.name) {
|
} else if (!partial.call && !options.name) {
|
||||||
// This is a dynamic partial that returned a string
|
// This is a dynamic partial that returned a string
|
||||||
options.name = partial;
|
options.name = partial;
|
||||||
partial = options.partials[partial];
|
partial = lookupOwnProperty(options.partials, partial);
|
||||||
}
|
}
|
||||||
return partial;
|
return partial;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invokePartial(partial, context, options) {
|
export function invokePartial(partial, context, options) {
|
||||||
// Use the current closure context to save the partial-block if this partial
|
// Use the current closure context to save the partial-block if this partial
|
||||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
const currentPartialBlock = lookupOwnProperty(options.data, 'partial-block');
|
||||||
options.partial = true;
|
options.partial = true;
|
||||||
if (options.ids) {
|
if (options.ids) {
|
||||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||||
@@ -410,6 +404,12 @@ export function noop() {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lookupOwnProperty(obj, name) {
|
||||||
|
if (obj && Object.prototype.hasOwnProperty.call(obj, name)) {
|
||||||
|
return obj[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initData(context, data) {
|
function initData(context, data) {
|
||||||
if (!data || !('root' in data)) {
|
if (!data || !('root' in data)) {
|
||||||
data = data ? createFrame(data) : {};
|
data = data ? createFrame(data) : {};
|
||||||
@@ -435,9 +435,10 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
|||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
function addHelpers(mergedHelpers, helpers, container) {
|
||||||
Object.keys(mergedHelpers).forEach(helperName => {
|
if (!helpers) return;
|
||||||
let helper = mergedHelpers[helperName];
|
Object.keys(helpers).forEach(helperName => {
|
||||||
|
let helper = helpers[helperName];
|
||||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -445,6 +446,7 @@ function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
|||||||
function passLookupPropertyOption(helper, container) {
|
function passLookupPropertyOption(helper, container) {
|
||||||
const lookupProperty = container.lookupProperty;
|
const lookupProperty = container.lookupProperty;
|
||||||
return wrapHelper(helper, options => {
|
return wrapHelper(helper, options => {
|
||||||
return Utils.extend({ lookupProperty }, options);
|
options.lookupProperty = lookupProperty;
|
||||||
|
return options;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,3 +114,30 @@ export function blockParams(params, ids) {
|
|||||||
export function appendContextPath(contextPath, id) {
|
export function appendContextPath(contextPath, id) {
|
||||||
return (contextPath ? contextPath + '.' : '') + id;
|
return (contextPath ? contextPath + '.' : '') + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coerce an untrusted depth value to a safe non-negative integer.
|
||||||
|
* Returns `0` for any value that is not a finite, non-negative number.
|
||||||
|
*
|
||||||
|
* @param {unknown} depth - The depth value to sanitize.
|
||||||
|
* @returns {number} A non-negative integer.
|
||||||
|
*/
|
||||||
|
export function sanitizeDepth(depth) {
|
||||||
|
let number = Number(depth);
|
||||||
|
if (!Number.isFinite(number) || number < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.floor(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a sanitized copy of a PathExpression AST node's parts array.
|
||||||
|
* Coerces each element to a string, or returns an empty array if parts
|
||||||
|
* is not an array.
|
||||||
|
*
|
||||||
|
* @param {unknown} parts - The parts value to sanitize.
|
||||||
|
* @returns {string[]} A safe string array.
|
||||||
|
*/
|
||||||
|
export function sanitizeParts(parts) {
|
||||||
|
return Array.isArray(parts) ? parts.map(String) : [];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// USAGE:
|
// USAGE:
|
||||||
// var handlebars = require('handlebars');
|
// var handlebars = require('handlebars');
|
||||||
|
/* eslint-env node */
|
||||||
/* eslint-disable no-var */
|
/* eslint-disable no-var */
|
||||||
|
|
||||||
// var local = handlebars.create();
|
// var local = handlebars.create();
|
||||||
|
|||||||
+46
-8
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-env node */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import Async from 'neo-async';
|
import Async from 'neo-async';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
@@ -195,16 +196,24 @@ module.exports.cli = function(opts) {
|
|||||||
|
|
||||||
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||||
|
|
||||||
|
if (opts.namespace && !isValidNamespace(opts.namespace)) {
|
||||||
|
throw new Handlebars.Exception('Invalid namespace format');
|
||||||
|
}
|
||||||
|
|
||||||
let output = new SourceNode();
|
let output = new SourceNode();
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
if (opts.amd) {
|
if (opts.amd) {
|
||||||
|
const runtimeModulePath =
|
||||||
|
(opts.handlebarPath || '') + 'handlebars.runtime';
|
||||||
output.add(
|
output.add(
|
||||||
"define(['" +
|
'define([' +
|
||||||
opts.handlebarPath +
|
quoteForJavaScript(runtimeModulePath) +
|
||||||
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||||
);
|
);
|
||||||
} else if (opts.commonjs) {
|
} else if (opts.commonjs) {
|
||||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
output.add(
|
||||||
|
'var Handlebars = require(' + quoteForJavaScript(opts.commonjs) + ');'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
output.add('(function() {\n');
|
output.add('(function() {\n');
|
||||||
}
|
}
|
||||||
@@ -254,9 +263,9 @@ module.exports.cli = function(opts) {
|
|||||||
}
|
}
|
||||||
output.add([
|
output.add([
|
||||||
objectName,
|
objectName,
|
||||||
"['",
|
'[',
|
||||||
template.name,
|
quoteForJavaScript(template.name),
|
||||||
"'] = template(",
|
'] = template(',
|
||||||
precompiled,
|
precompiled,
|
||||||
');\n'
|
');\n'
|
||||||
]);
|
]);
|
||||||
@@ -276,7 +285,9 @@ module.exports.cli = function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts.map) {
|
if (opts.map) {
|
||||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
output.add(
|
||||||
|
'\n//# sourceMappingURL=' + sanitizeSourceMapComment(opts.map) + '\n'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
output = output.toStringWithSourceMap();
|
output = output.toStringWithSourceMap();
|
||||||
@@ -306,6 +317,33 @@ function arrayCast(value) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Safely quotes a value for embedding in generated JavaScript strings
|
||||||
|
*
|
||||||
|
* Uses JSON.stringify which handles all special characters.
|
||||||
|
*/
|
||||||
|
function quoteForJavaScript(value) {
|
||||||
|
return JSON.stringify(String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a namespace is a legitimate dotted JavaScript identifier
|
||||||
|
* (e.g. "App.templates") to prevent arbitrary code injection
|
||||||
|
*/
|
||||||
|
function isValidNamespace(namespace) {
|
||||||
|
return /^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(
|
||||||
|
namespace
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips line terminators from source map URLs to prevent injection of new
|
||||||
|
* JavaScript lines via the sourceMappingURL comment
|
||||||
|
*/
|
||||||
|
function sanitizeSourceMapComment(value) {
|
||||||
|
return String(value).replace(/[\r\n\u2028\u2029]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||||
*
|
*
|
||||||
|
|||||||
Generated
+13664
-5741
File diff suppressed because it is too large
Load Diff
+23
-22
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"barename": "handlebars",
|
"barename": "handlebars",
|
||||||
"version": "4.7.6",
|
"version": "4.7.9",
|
||||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||||
"homepage": "http://www.handlebarsjs.com/",
|
"homepage": "https://handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"mustache",
|
"mustache",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/wycats/handlebars.js.git"
|
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||||
},
|
},
|
||||||
"author": "Yehuda Katz",
|
"author": "Yehuda Katz",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"neo-async": "^2.6.0",
|
"neo-async": "^2.6.2",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"wordwrap": "^1.0.0"
|
"wordwrap": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"uglify-js": "^3.1.4"
|
"uglify-js": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@knappi/grunt-saucelabs": "^9.0.2",
|
"@playwright/test": "1.44.1",
|
||||||
"aws-sdk": "^2.1.49",
|
"aws-sdk": "^2.1.49",
|
||||||
"babel-loader": "^5.0.0",
|
"babel-loader": "^5.0.0",
|
||||||
"babel-runtime": "^5.1.10",
|
"babel-runtime": "^5.1.10",
|
||||||
@@ -39,17 +39,15 @@
|
|||||||
"chai-diff": "^1.0.1",
|
"chai-diff": "^1.0.1",
|
||||||
"concurrently": "^5.0.0",
|
"concurrently": "^5.0.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"dtslint": "^0.5.5",
|
|
||||||
"dustjs-linkedin": "^2.0.2",
|
"dustjs-linkedin": "^2.0.2",
|
||||||
"eco": "~1.1.0-rc-3",
|
"eco": "~1.1.0-rc-3",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"eslint-plugin-compat": "^3.3.0",
|
"eslint-plugin-compat": "^3.13.0",
|
||||||
"eslint-plugin-es5": "^1.4.1",
|
"eslint-plugin-es5": "^1.4.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"grunt": "^1.0.4",
|
"grunt": "1.5.3",
|
||||||
"grunt-babel": "^5.0.0",
|
"grunt-babel": "^5.0.0",
|
||||||
"grunt-bg-shell": "^2.3.3",
|
|
||||||
"grunt-cli": "^1",
|
"grunt-cli": "^1",
|
||||||
"grunt-contrib-clean": "^1",
|
"grunt-contrib-clean": "^1",
|
||||||
"grunt-contrib-concat": "^1",
|
"grunt-contrib-concat": "^1",
|
||||||
@@ -58,6 +56,7 @@
|
|||||||
"grunt-contrib-requirejs": "^1",
|
"grunt-contrib-requirejs": "^1",
|
||||||
"grunt-contrib-uglify": "^1",
|
"grunt-contrib-uglify": "^1",
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"grunt-shell": "^4.0.0",
|
||||||
"grunt-webpack": "^1.0.8",
|
"grunt-webpack": "^1.0.8",
|
||||||
"husky": "^3.1.0",
|
"husky": "^3.1.0",
|
||||||
"jison": "~0.3.0",
|
"jison": "~0.3.0",
|
||||||
@@ -76,23 +75,26 @@
|
|||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
"browser": {
|
"browser": "./dist/cjs/handlebars.js",
|
||||||
".": "./dist/cjs/handlebars.js",
|
|
||||||
"./runtime": "./dist/cjs/handlebars.runtime.js"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"handlebars": "bin/handlebars"
|
"handlebars": "bin/handlebars"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "grunt build",
|
||||||
|
"release": "npm run build && grunt release",
|
||||||
"format": "prettier --write '**/*.js' && eslint --fix .",
|
"format": "prettier --write '**/*.js' && eslint --fix .",
|
||||||
"check-format": "prettier --check '**/*.js'",
|
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||||
"lint": "eslint --max-warnings 0 .",
|
"lint:eslint": "eslint --max-warnings 0 .",
|
||||||
"dtslint": "dtslint types",
|
"lint:prettier": "prettier --check '**/*.js'",
|
||||||
"test": "grunt",
|
"lint:types": "tsc --noEmit --project types",
|
||||||
"extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",
|
"test": "npm run test:mocha",
|
||||||
"integration-test": "grunt integration-tests",
|
"test:mocha": "grunt build && grunt test",
|
||||||
|
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
|
||||||
|
"test:integration": "grunt integration-tests",
|
||||||
|
"test:serve": "grunt connect:server:keepalive",
|
||||||
|
"extensive-tests-and-publish-to-aws": "npx mocha tasks/tests/ && grunt --stack extensive-tests-and-publish-to-aws",
|
||||||
"--- combined tasks ---": "",
|
"--- combined tasks ---": "",
|
||||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:dtslint npm:check-format npm:test"
|
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||||
},
|
},
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
@@ -109,7 +111,6 @@
|
|||||||
"dist/amd/**/*.js",
|
"dist/amd/**/*.js",
|
||||||
"dist/cjs/**/*.js",
|
"dist/cjs/**/*.js",
|
||||||
"lib",
|
"lib",
|
||||||
"print-script",
|
|
||||||
"release-notes.md",
|
"release-notes.md",
|
||||||
"runtime.js",
|
"runtime.js",
|
||||||
"types/*.d.ts",
|
"types/*.d.ts",
|
||||||
@@ -121,7 +122,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,css,json,md}": [
|
"*.{js,css,json}": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"git add"
|
"git add"
|
||||||
],
|
],
|
||||||
|
|||||||
+41
-1
@@ -2,7 +2,47 @@
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...master)
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...master)
|
||||||
|
|
||||||
|
## v4.7.9 - March 26th, 2026
|
||||||
|
- fix: enable shell mode for spawn to resolve Windows EINVAL issue - e0137c2
|
||||||
|
- fix type "RuntimeOptions" also accepting string partials - eab1d14
|
||||||
|
- feat(types): set `hash` to be a `Record<string, any>` - de4414d
|
||||||
|
- fix non-contiguous program indices - 4512766
|
||||||
|
- refactor: rename i to startPartIndex - e497a35
|
||||||
|
- security: fix security issues - 68d8df5
|
||||||
|
|
||||||
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9)
|
||||||
|
|
||||||
|
## v4.7.8 - July 27th, 2023
|
||||||
|
|
||||||
|
- Make library compatible with workers (#1894) - 3d3796c
|
||||||
|
- Don't rely on Node.js global object (#1776) - 2954e7e
|
||||||
|
- Fix compiling of each block params in strict mode (#1855) - 30dbf04
|
||||||
|
- Fix rollup warning when importing Handlebars as ESM - 03d387b
|
||||||
|
- Fix bundler issue with webpack 5 (#1862) - c6c6bbb
|
||||||
|
- Use https instead of git for mustache submodule - 88ac068
|
||||||
|
|
||||||
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...v4.7.8)
|
||||||
|
|
||||||
|
## 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
|
## v4.7.6 - April 3rd, 2020
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
+387
-399
@@ -6,117 +6,156 @@ beforeEach(function() {
|
|||||||
|
|
||||||
describe('basic context', function() {
|
describe('basic context', function() {
|
||||||
it('most basic', function() {
|
it('most basic', function() {
|
||||||
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ foo: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escaping', function() {
|
it('escaping', function() {
|
||||||
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
expectTemplate('\\{{foo}}')
|
||||||
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
.withInput({ foo: 'food' })
|
||||||
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
.toCompileTo('{{foo}}');
|
||||||
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
|
|
||||||
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
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() {
|
it('compiling with a basic context', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
'Goodbye\n{{cruel}}\n{{world}}!',
|
.withInput({
|
||||||
{ cruel: 'cruel', world: 'world' },
|
cruel: 'cruel',
|
||||||
'Goodbye\ncruel\nworld!',
|
world: 'world'
|
||||||
'It works if all the required keys are provided'
|
})
|
||||||
);
|
.withMessage('It works if all the required keys are provided')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('compiling with a string context', function() {
|
it('compiling with a string context', function() {
|
||||||
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
|
expectTemplate('{{.}}{{length}}')
|
||||||
|
.withInput('bye')
|
||||||
|
.toCompileTo('bye3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('compiling with an undefined context', function() {
|
it('compiling with an undefined context', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
|
||||||
'Goodbye\n{{cruel}}\n{{world.bar}}!',
|
.withInput(undefined)
|
||||||
undefined,
|
.toCompileTo('Goodbye\n\n!');
|
||||||
'Goodbye\n\n!'
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
|
||||||
'{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}',
|
.withInput(undefined)
|
||||||
undefined,
|
.toCompileTo('Goodbye');
|
||||||
'Goodbye'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('comments', function() {
|
it('comments', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
'{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
.withInput({
|
||||||
{ cruel: 'cruel', world: 'world' },
|
cruel: 'cruel',
|
||||||
'Goodbye\ncruel\nworld!',
|
world: 'world'
|
||||||
'comments are ignored'
|
})
|
||||||
|
.withMessage('comments are ignored')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
|
|
||||||
|
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
|
||||||
|
'blah'
|
||||||
);
|
);
|
||||||
|
|
||||||
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
|
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
|
||||||
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
|
||||||
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
|
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
|
||||||
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
|
' blah'
|
||||||
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
|
);
|
||||||
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
|
|
||||||
|
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
|
||||||
|
' blah'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('boolean', function() {
|
it('boolean', function() {
|
||||||
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||||
shouldCompileTo(
|
expectTemplate(string)
|
||||||
string,
|
.withInput({
|
||||||
{ goodbye: true, world: 'world' },
|
goodbye: true,
|
||||||
'GOODBYE cruel world!',
|
world: 'world'
|
||||||
'booleans show the contents when true'
|
})
|
||||||
);
|
.withMessage('booleans show the contents when true')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate(string)
|
||||||
string,
|
.withInput({
|
||||||
{ goodbye: false, world: 'world' },
|
goodbye: false,
|
||||||
'cruel world!',
|
world: 'world'
|
||||||
'booleans do not show the contents when false'
|
})
|
||||||
);
|
.withMessage('booleans do not show the contents when false')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('zeros', function() {
|
it('zeros', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('num1: {{num1}}, num2: {{num2}}')
|
||||||
'num1: {{num1}}, num2: {{num2}}',
|
.withInput({
|
||||||
{ num1: 42, num2: 0 },
|
num1: 42,
|
||||||
'num1: 42, num2: 0'
|
num2: 0
|
||||||
);
|
})
|
||||||
shouldCompileTo('num: {{.}}', 0, 'num: 0');
|
.toCompileTo('num1: 42, num2: 0');
|
||||||
shouldCompileTo('num: {{num1/num2}}', { num1: { num2: 0 } }, 'num: 0');
|
|
||||||
|
expectTemplate('num: {{.}}')
|
||||||
|
.withInput(0)
|
||||||
|
.toCompileTo('num: 0');
|
||||||
|
|
||||||
|
expectTemplate('num: {{num1/num2}}')
|
||||||
|
.withInput({ num1: { num2: 0 } })
|
||||||
|
.toCompileTo('num: 0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('false', function() {
|
it('false', function() {
|
||||||
/* eslint-disable no-new-wrappers */
|
/* eslint-disable no-new-wrappers */
|
||||||
shouldCompileTo(
|
expectTemplate('val1: {{val1}}, val2: {{val2}}')
|
||||||
'val1: {{val1}}, val2: {{val2}}',
|
.withInput({
|
||||||
{ val1: false, val2: new Boolean(false) },
|
val1: false,
|
||||||
'val1: false, val2: false'
|
val2: new Boolean(false)
|
||||||
);
|
})
|
||||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
.toCompileTo('val1: false, val2: false');
|
||||||
shouldCompileTo(
|
|
||||||
'val: {{val1/val2}}',
|
|
||||||
{ val1: { val2: false } },
|
|
||||||
'val: false'
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('val: {{.}}')
|
||||||
'val1: {{{val1}}}, val2: {{{val2}}}',
|
.withInput(false)
|
||||||
{ val1: false, val2: new Boolean(false) },
|
.toCompileTo('val: false');
|
||||||
'val1: false, val2: false'
|
|
||||||
);
|
expectTemplate('val: {{val1/val2}}')
|
||||||
shouldCompileTo(
|
.withInput({ val1: { val2: false } })
|
||||||
'val: {{{val1/val2}}}',
|
.toCompileTo('val: false');
|
||||||
{ val1: { val2: false } },
|
|
||||||
'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 */
|
/* eslint-enable */
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined and null', function() {
|
it('should handle undefined and null', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome undefined null}}')
|
||||||
'{{awesome undefined null}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function(_undefined, _null, options) {
|
awesome: function(_undefined, _null, options) {
|
||||||
return (
|
return (
|
||||||
(_undefined === undefined) +
|
(_undefined === undefined) +
|
||||||
@@ -126,373 +165,325 @@ describe('basic context', function() {
|
|||||||
typeof options
|
typeof options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'true true object'
|
.toCompileTo('true true object');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{undefined}}')
|
||||||
'{{undefined}}',
|
.withInput({
|
||||||
{
|
|
||||||
undefined: function() {
|
undefined: function() {
|
||||||
return 'undefined!';
|
return 'undefined!';
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'undefined!'
|
.toCompileTo('undefined!');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{null}}')
|
||||||
'{{null}}',
|
.withInput({
|
||||||
{
|
|
||||||
null: function() {
|
null: function() {
|
||||||
return 'null!';
|
return 'null!';
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'null!'
|
.toCompileTo('null!');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('newlines', function() {
|
it('newlines', function() {
|
||||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
|
||||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
|
||||||
|
expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escaping text', function() {
|
it('escaping text', function() {
|
||||||
shouldCompileTo(
|
expectTemplate("Awesome's")
|
||||||
"Awesome's",
|
.withMessage(
|
||||||
{},
|
"text is escaped so that it doesn't get caught on single quotes"
|
||||||
"Awesome's",
|
)
|
||||||
"text is escaped so that it doesn't get caught on single quotes"
|
.toCompileTo("Awesome's");
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('Awesome\\')
|
||||||
'Awesome\\',
|
.withMessage("text is escaped so that the closing quote can't be ignored")
|
||||||
{},
|
.toCompileTo('Awesome\\');
|
||||||
'Awesome\\',
|
|
||||||
"text is escaped so that the closing quote can't be ignored"
|
expectTemplate('Awesome\\\\ foo')
|
||||||
);
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
shouldCompileTo(
|
.toCompileTo('Awesome\\\\ foo');
|
||||||
'Awesome\\\\ foo',
|
|
||||||
{},
|
expectTemplate('Awesome {{foo}}')
|
||||||
'Awesome\\\\ foo',
|
.withInput({ foo: '\\' })
|
||||||
"text is escaped so that it doesn't mess up backslashes"
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
);
|
.toCompileTo('Awesome \\');
|
||||||
shouldCompileTo(
|
|
||||||
'Awesome {{foo}}',
|
expectTemplate(" ' ' ")
|
||||||
{ foo: '\\' },
|
.withMessage('double quotes never produce invalid javascript')
|
||||||
'Awesome \\',
|
.toCompileTo(" ' ' ");
|
||||||
"text is escaped so that it doesn't mess up backslashes"
|
|
||||||
);
|
|
||||||
shouldCompileTo(
|
|
||||||
" ' ' ",
|
|
||||||
{},
|
|
||||||
" ' ' ",
|
|
||||||
'double quotes never produce invalid javascript'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escaping expressions', function() {
|
it('escaping expressions', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{{awesome}}}')
|
||||||
'{{{awesome}}}',
|
.withInput({ awesome: "&'\\<>" })
|
||||||
{ awesome: "&'\\<>" },
|
.withMessage("expressions with 3 handlebars aren't escaped")
|
||||||
"&'\\<>",
|
.toCompileTo("&'\\<>");
|
||||||
"expressions with 3 handlebars aren't escaped"
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{&awesome}}')
|
||||||
'{{&awesome}}',
|
.withInput({ awesome: "&'\\<>" })
|
||||||
{ awesome: "&'\\<>" },
|
.withMessage("expressions with {{& handlebars aren't escaped")
|
||||||
"&'\\<>",
|
.toCompileTo("&'\\<>");
|
||||||
"expressions with {{& handlebars aren't escaped"
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome}}')
|
||||||
'{{awesome}}',
|
.withInput({ awesome: '&"\'`\\<>' })
|
||||||
{ awesome: '&"\'`\\<>' },
|
.withMessage('by default expressions should be escaped')
|
||||||
'&"'`\\<>',
|
.toCompileTo('&"'`\\<>');
|
||||||
'by default expressions should be escaped'
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome}}')
|
||||||
'{{awesome}}',
|
.withInput({ awesome: 'Escaped, <b> looks like: <b>' })
|
||||||
{ awesome: 'Escaped, <b> looks like: <b>' },
|
.withMessage('escaping should properly handle amperstands')
|
||||||
'Escaped, <b> looks like: &lt;b&gt;',
|
.toCompileTo('Escaped, <b> looks like: &lt;b&gt;');
|
||||||
'escaping should properly handle amperstands'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("functions returning safestrings shouldn't be escaped", function() {
|
it("functions returning safestrings shouldn't be escaped", function() {
|
||||||
var hash = {
|
expectTemplate('{{awesome}}')
|
||||||
awesome: function() {
|
.withInput({
|
||||||
return new Handlebars.SafeString("&'\\<>");
|
awesome: function() {
|
||||||
}
|
return new Handlebars.SafeString("&'\\<>");
|
||||||
};
|
}
|
||||||
shouldCompileTo(
|
})
|
||||||
'{{awesome}}',
|
.withMessage("functions returning safestrings aren't escaped")
|
||||||
hash,
|
.toCompileTo("&'\\<>");
|
||||||
"&'\\<>",
|
|
||||||
"functions returning safestrings aren't escaped"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('functions', function() {
|
it('functions', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome}}')
|
||||||
'{{awesome}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function() {
|
awesome: function() {
|
||||||
return 'Awesome';
|
return 'Awesome';
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'Awesome',
|
.withMessage('functions are called and render their output')
|
||||||
'functions are called and render their output'
|
.toCompileTo('Awesome');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome}}')
|
||||||
'{{awesome}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function() {
|
awesome: function() {
|
||||||
return this.more;
|
return this.more;
|
||||||
},
|
},
|
||||||
more: 'More awesome'
|
more: 'More awesome'
|
||||||
},
|
})
|
||||||
'More awesome',
|
.withMessage('functions are bound to the context')
|
||||||
'functions are bound to the context'
|
.toCompileTo('More awesome');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('functions with context argument', function() {
|
it('functions with context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{awesome frank}}')
|
||||||
'{{awesome frank}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function(context) {
|
awesome: function(context) {
|
||||||
return context;
|
return context;
|
||||||
},
|
},
|
||||||
frank: 'Frank'
|
frank: 'Frank'
|
||||||
},
|
})
|
||||||
'Frank',
|
.withMessage('functions are called with context arguments')
|
||||||
'functions are called with context arguments'
|
.toCompileTo('Frank');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pathed functions with context argument', function() {
|
it('pathed functions with context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{bar.awesome frank}}')
|
||||||
'{{bar.awesome frank}}',
|
.withInput({
|
||||||
{
|
|
||||||
bar: {
|
bar: {
|
||||||
awesome: function(context) {
|
awesome: function(context) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
frank: 'Frank'
|
frank: 'Frank'
|
||||||
},
|
})
|
||||||
'Frank',
|
.withMessage('functions are called with context arguments')
|
||||||
'functions are called with context arguments'
|
.toCompileTo('Frank');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depthed functions with context argument', function() {
|
it('depthed functions with context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
|
||||||
'{{#with frank}}{{../awesome .}}{{/with}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function(context) {
|
awesome: function(context) {
|
||||||
return context;
|
return context;
|
||||||
},
|
},
|
||||||
frank: 'Frank'
|
frank: 'Frank'
|
||||||
},
|
})
|
||||||
'Frank',
|
.withMessage('functions are called with context arguments')
|
||||||
'functions are called with context arguments'
|
.toCompileTo('Frank');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block functions with context argument', function() {
|
it('block functions with context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
|
||||||
'{{#awesome 1}}inner {{.}}{{/awesome}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function(context, options) {
|
awesome: function(context, options) {
|
||||||
return options.fn(context);
|
return options.fn(context);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'inner 1',
|
.withMessage('block functions are called with context and options')
|
||||||
'block functions are called with context and options'
|
.toCompileTo('inner 1');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depthed block functions with context argument', function() {
|
it('depthed block functions with context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate(
|
||||||
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
|
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
|
||||||
{
|
)
|
||||||
|
.withInput({
|
||||||
value: true,
|
value: true,
|
||||||
awesome: function(context, options) {
|
awesome: function(context, options) {
|
||||||
return options.fn(context);
|
return options.fn(context);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'inner 1',
|
.withMessage('block functions are called with context and options')
|
||||||
'block functions are called with context and options'
|
.toCompileTo('inner 1');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block functions without context argument', function() {
|
it('block functions without context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#awesome}}inner{{/awesome}}')
|
||||||
'{{#awesome}}inner{{/awesome}}',
|
.withInput({
|
||||||
{
|
|
||||||
awesome: function(options) {
|
awesome: function(options) {
|
||||||
return options.fn(this);
|
return options.fn(this);
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'inner',
|
.withMessage('block functions are called with options')
|
||||||
'block functions are called with options'
|
.toCompileTo('inner');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pathed block functions without context argument', function() {
|
it('pathed block functions without context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
|
||||||
'{{#foo.awesome}}inner{{/foo.awesome}}',
|
.withInput({
|
||||||
{
|
|
||||||
foo: {
|
foo: {
|
||||||
awesome: function() {
|
awesome: function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'inner',
|
.withMessage('block functions are called with options')
|
||||||
'block functions are called with options'
|
.toCompileTo('inner');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depthed block functions without context argument', function() {
|
it('depthed block functions without context argument', function() {
|
||||||
shouldCompileTo(
|
expectTemplate(
|
||||||
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
|
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
|
||||||
{
|
)
|
||||||
|
.withInput({
|
||||||
value: true,
|
value: true,
|
||||||
awesome: function() {
|
awesome: function() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'inner',
|
.withMessage('block functions are called with options')
|
||||||
'block functions are called with options'
|
.toCompileTo('inner');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('paths with hyphens', function() {
|
it('paths with hyphens', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{foo-bar}}')
|
||||||
'{{foo-bar}}',
|
.withInput({ 'foo-bar': 'baz' })
|
||||||
{ 'foo-bar': 'baz' },
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
'baz',
|
.toCompileTo('baz');
|
||||||
'Paths can contain hyphens (-)'
|
|
||||||
);
|
expectTemplate('{{foo.foo-bar}}')
|
||||||
shouldCompileTo(
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
'{{foo.foo-bar}}',
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
{ foo: { 'foo-bar': 'baz' } },
|
.toCompileTo('baz');
|
||||||
'baz',
|
|
||||||
'Paths can contain hyphens (-)'
|
expectTemplate('{{foo/foo-bar}}')
|
||||||
);
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
shouldCompileTo(
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
'{{foo/foo-bar}}',
|
.toCompileTo('baz');
|
||||||
{ foo: { 'foo-bar': 'baz' } },
|
|
||||||
'baz',
|
|
||||||
'Paths can contain hyphens (-)'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested paths', function() {
|
it('nested paths', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
'Goodbye {{alan/expression}} world!',
|
.withInput({ alan: { expression: 'beautiful' } })
|
||||||
{ alan: { expression: 'beautiful' } },
|
.withMessage('Nested paths access nested objects')
|
||||||
'Goodbye beautiful world!',
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
'Nested paths access nested objects'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested paths with empty string value', function() {
|
it('nested paths with empty string value', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
'Goodbye {{alan/expression}} world!',
|
.withInput({ alan: { expression: '' } })
|
||||||
{ alan: { expression: '' } },
|
.withMessage('Nested paths access nested objects with empty string')
|
||||||
'Goodbye world!',
|
.toCompileTo('Goodbye world!');
|
||||||
'Nested paths access nested objects with empty string'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('literal paths', function() {
|
it('literal paths', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye {{[@alan]/expression}} world!')
|
||||||
'Goodbye {{[@alan]/expression}} world!',
|
.withInput({ '@alan': { expression: 'beautiful' } })
|
||||||
{ '@alan': { expression: 'beautiful' } },
|
.withMessage('Literal paths can be used')
|
||||||
'Goodbye beautiful world!',
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
'Literal paths can be used'
|
|
||||||
);
|
expectTemplate('Goodbye {{[foo bar]/expression}} world!')
|
||||||
shouldCompileTo(
|
.withInput({ 'foo bar': { expression: 'beautiful' } })
|
||||||
'Goodbye {{[foo bar]/expression}} world!',
|
.withMessage('Literal paths can be used')
|
||||||
{ 'foo bar': { expression: 'beautiful' } },
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
'Goodbye beautiful world!',
|
|
||||||
'Literal paths can be used'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('literal references', function() {
|
it('literal references', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('Goodbye {{[foo bar]}} world!')
|
||||||
'Goodbye {{[foo bar]}} world!',
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
{ 'foo bar': 'beautiful' },
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
'Goodbye beautiful world!'
|
|
||||||
);
|
expectTemplate('Goodbye {{"foo bar"}} world!')
|
||||||
shouldCompileTo(
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
'Goodbye {{"foo bar"}} world!',
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
{ 'foo bar': 'beautiful' },
|
|
||||||
'Goodbye beautiful world!'
|
expectTemplate("Goodbye {{'foo bar'}} world!")
|
||||||
);
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
shouldCompileTo(
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
"Goodbye {{'foo bar'}} world!",
|
|
||||||
{ 'foo bar': 'beautiful' },
|
expectTemplate('Goodbye {{"foo[bar"}} world!')
|
||||||
'Goodbye beautiful world!'
|
.withInput({ 'foo[bar': 'beautiful' })
|
||||||
);
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
shouldCompileTo(
|
|
||||||
'Goodbye {{"foo[bar"}} world!',
|
expectTemplate('Goodbye {{"foo\'bar"}} world!')
|
||||||
{ 'foo[bar': 'beautiful' },
|
.withInput({ "foo'bar": 'beautiful' })
|
||||||
'Goodbye beautiful world!'
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate("Goodbye {{'foo\"bar'}} world!")
|
||||||
'Goodbye {{"foo\'bar"}} world!',
|
.withInput({ 'foo"bar': 'beautiful' })
|
||||||
{ "foo'bar": 'beautiful' },
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
'Goodbye beautiful world!'
|
|
||||||
);
|
|
||||||
shouldCompileTo(
|
|
||||||
"Goodbye {{'foo\"bar'}} world!",
|
|
||||||
{ 'foo"bar': 'beautiful' },
|
|
||||||
'Goodbye beautiful world!'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||||
shouldCompileTo('test: {{.}}', [null, { helper: 'awesome' }], 'test: ');
|
expectTemplate('test: {{.}}')
|
||||||
|
.withInput(null)
|
||||||
|
.withHelpers({ helper: 'awesome' })
|
||||||
|
.toCompileTo('test: ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('complex but empty paths', function() {
|
it('complex but empty paths', function() {
|
||||||
shouldCompileTo('{{person/name}}', { person: { name: null } }, '');
|
expectTemplate('{{person/name}}')
|
||||||
shouldCompileTo('{{person/name}}', { person: {} }, '');
|
.withInput({ person: { name: null } })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{person/name}}')
|
||||||
|
.withInput({ person: {} })
|
||||||
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword in paths', function() {
|
it('this keyword in paths', function() {
|
||||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
|
||||||
var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
shouldCompileTo(
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
string,
|
.toCompileTo('goodbyeGoodbyeGOODBYE');
|
||||||
hash,
|
|
||||||
'goodbyeGoodbyeGOODBYE',
|
|
||||||
'This keyword in paths evaluates to current context'
|
|
||||||
);
|
|
||||||
|
|
||||||
string = '{{#hellos}}{{this/text}}{{/hellos}}';
|
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
|
||||||
hash = {
|
.withInput({
|
||||||
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||||
};
|
})
|
||||||
shouldCompileTo(
|
.withMessage('This keyword evaluates in more complex paths')
|
||||||
string,
|
.toCompileTo('helloHelloHELLO');
|
||||||
hash,
|
|
||||||
'helloHelloHELLO',
|
|
||||||
'This keyword evaluates in more complex paths'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword nested inside path', function() {
|
it('this keyword nested inside path', function() {
|
||||||
shouldThrow(
|
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
|
||||||
function() {
|
|
||||||
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
|
|
||||||
},
|
|
||||||
Error,
|
Error,
|
||||||
'Invalid path: text/this - 1:13'
|
'Invalid path: text/this - 1:13'
|
||||||
);
|
);
|
||||||
|
|
||||||
shouldCompileTo('{{[this]}}', { this: 'bar' }, 'bar');
|
expectTemplate('{{[this]}}')
|
||||||
shouldCompileTo('{{text/[this]}}', { text: { this: 'bar' } }, 'bar');
|
.withInput({ this: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{text/[this]}}')
|
||||||
|
.withInput({ text: { this: 'bar' } })
|
||||||
|
.toCompileTo('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('this keyword in helpers', function() {
|
it('this keyword in helpers', function() {
|
||||||
@@ -501,108 +492,105 @@ describe('basic context', function() {
|
|||||||
return 'bar ' + value;
|
return 'bar ' + value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
|
|
||||||
var hash = { goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] };
|
|
||||||
shouldCompileTo(
|
|
||||||
string,
|
|
||||||
[hash, helpers],
|
|
||||||
'bar goodbyebar Goodbyebar GOODBYE',
|
|
||||||
'This keyword in paths evaluates to current context'
|
|
||||||
);
|
|
||||||
|
|
||||||
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
|
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
|
||||||
hash = {
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
.withHelpers(helpers)
|
||||||
};
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
shouldCompileTo(
|
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
|
||||||
string,
|
|
||||||
[hash, helpers],
|
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
|
||||||
'bar hellobar Hellobar HELLO',
|
.withInput({
|
||||||
'This keyword evaluates in more complex paths'
|
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() {
|
it('this keyword nested inside helpers param', function() {
|
||||||
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
|
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
|
||||||
shouldThrow(
|
|
||||||
function() {
|
|
||||||
CompilerContext.compile(string);
|
|
||||||
},
|
|
||||||
Error,
|
Error,
|
||||||
'Invalid path: text/this - 1:17'
|
'Invalid path: text/this - 1:17'
|
||||||
);
|
);
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{foo [this]}}')
|
||||||
'{{foo [this]}}',
|
.withInput({
|
||||||
{
|
|
||||||
foo: function(value) {
|
foo: function(value) {
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
this: 'bar'
|
this: 'bar'
|
||||||
},
|
})
|
||||||
'bar'
|
.toCompileTo('bar');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{foo text/[this]}}')
|
||||||
'{{foo text/[this]}}',
|
.withInput({
|
||||||
{
|
|
||||||
foo: function(value) {
|
foo: function(value) {
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
text: { this: 'bar' }
|
text: { this: 'bar' }
|
||||||
},
|
})
|
||||||
'bar'
|
.toCompileTo('bar');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass string literals', function() {
|
it('pass string literals', function() {
|
||||||
shouldCompileTo('{{"foo"}}', {}, '');
|
expectTemplate('{{"foo"}}').toCompileTo('');
|
||||||
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{"foo"}}')
|
||||||
'{{#"foo"}}{{.}}{{/"foo"}}',
|
.withInput({ foo: 'bar' })
|
||||||
{ foo: ['bar', 'baz'] },
|
.toCompileTo('bar');
|
||||||
'barbaz'
|
|
||||||
);
|
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
|
||||||
|
.withInput({
|
||||||
|
foo: ['bar', 'baz']
|
||||||
|
})
|
||||||
|
.toCompileTo('barbaz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass number literals', function() {
|
it('pass number literals', function() {
|
||||||
shouldCompileTo('{{12}}', {}, '');
|
expectTemplate('{{12}}').toCompileTo('');
|
||||||
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
|
|
||||||
shouldCompileTo('{{12.34}}', {}, '');
|
expectTemplate('{{12}}')
|
||||||
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
|
.withInput({ '12': 'bar' })
|
||||||
shouldCompileTo(
|
.toCompileTo('bar');
|
||||||
'{{12.34 1}}',
|
|
||||||
{
|
expectTemplate('{{12.34}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34}}')
|
||||||
|
.withInput({ '12.34': 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34 1}}')
|
||||||
|
.withInput({
|
||||||
'12.34': function(arg) {
|
'12.34': function(arg) {
|
||||||
return 'bar' + arg;
|
return 'bar' + arg;
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'bar1'
|
.toCompileTo('bar1');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass boolean literals', function() {
|
it('pass boolean literals', function() {
|
||||||
shouldCompileTo('{{true}}', {}, '');
|
expectTemplate('{{true}}').toCompileTo('');
|
||||||
shouldCompileTo('{{true}}', { '': 'foo' }, '');
|
|
||||||
shouldCompileTo('{{false}}', { false: 'foo' }, 'foo');
|
expectTemplate('{{true}}')
|
||||||
|
.withInput({ '': 'foo' })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{false}}')
|
||||||
|
.withInput({ false: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle literals in subexpression', function() {
|
it('should handle literals in subexpression', function() {
|
||||||
var helpers = {
|
expectTemplate('{{foo (false)}}')
|
||||||
foo: function(arg) {
|
.withInput({
|
||||||
|
false: function() {
|
||||||
|
return 'bar';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withHelper('foo', function(arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('bar');
|
||||||
shouldCompileTo(
|
|
||||||
'{{foo (false)}}',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
false: function() {
|
|
||||||
return 'bar';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
helpers
|
|
||||||
],
|
|
||||||
'bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+286
-335
@@ -1,455 +1,405 @@
|
|||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it('array', function() {
|
it('array', function() {
|
||||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||||
var hash = {
|
|
||||||
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(
|
expectTemplate(string)
|
||||||
string,
|
.withInput({
|
||||||
{ goodbyes: [], world: 'world' },
|
goodbyes: [
|
||||||
'cruel world!',
|
{ text: 'goodbye' },
|
||||||
'Arrays ignore the contents when empty'
|
{ 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() {
|
it('array without data', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
|
||||||
var hash = {
|
)
|
||||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
.withInput({
|
||||||
world: 'world'
|
goodbyes: [
|
||||||
};
|
{ text: 'goodbye' },
|
||||||
shouldCompileTo(
|
{ text: 'Goodbye' },
|
||||||
string,
|
{ text: 'GOODBYE' }
|
||||||
[hash, , , false],
|
],
|
||||||
'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'
|
world: 'world'
|
||||||
);
|
})
|
||||||
|
.withCompileOptions({ compat: false })
|
||||||
|
.toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('array with @index', function() {
|
it('array with @index', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
|
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
|
||||||
var hash = {
|
)
|
||||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
.withInput({
|
||||||
world: 'world'
|
goodbyes: [
|
||||||
};
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
var template = CompilerContext.compile(string);
|
{ text: 'GOODBYE' }
|
||||||
var result = template(hash);
|
],
|
||||||
|
world: 'world'
|
||||||
equal(
|
})
|
||||||
result,
|
.withMessage('The @index variable is used')
|
||||||
'0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!',
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
'The @index variable is used'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('empty block', function() {
|
it('empty block', function() {
|
||||||
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||||
var hash = {
|
|
||||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }],
|
|
||||||
world: 'world'
|
|
||||||
};
|
|
||||||
shouldCompileTo(
|
|
||||||
string,
|
|
||||||
hash,
|
|
||||||
'cruel world!',
|
|
||||||
'Arrays iterate over the contents when not empty'
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate(string)
|
||||||
string,
|
.withInput({
|
||||||
{ goodbyes: [], world: 'world' },
|
goodbyes: [
|
||||||
'cruel world!',
|
{ text: 'goodbye' },
|
||||||
'Arrays ignore the contents when empty'
|
{ 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() {
|
it('block with complex lookup', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
|
||||||
var hash = {
|
.withInput({
|
||||||
name: 'Alan',
|
name: 'Alan',
|
||||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
|
goodbyes: [
|
||||||
};
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
shouldCompileTo(
|
{ text: 'GOODBYE' }
|
||||||
string,
|
]
|
||||||
hash,
|
})
|
||||||
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
|
.withMessage(
|
||||||
'Templates can access variables in contexts up the stack with relative path syntax'
|
'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() {
|
it('multiple blocks with complex lookup', function() {
|
||||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}')
|
||||||
var hash = {
|
.withInput({
|
||||||
name: 'Alan',
|
name: 'Alan',
|
||||||
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]
|
goodbyes: [
|
||||||
};
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
{ text: 'GOODBYE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.toCompileTo('AlanAlanAlanAlanAlanAlan');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with complex lookup using nested context', function() {
|
it('block with complex lookup using nested context', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with deep nested complex lookup', function() {
|
it('block with deep nested complex lookup', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
|
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'
|
||||||
var hash = {
|
)
|
||||||
omg: 'OMG!',
|
.withInput({
|
||||||
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
omg: 'OMG!',
|
||||||
};
|
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
.toCompileTo('Goodbye cruel sad OMG!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with cached blocks', function() {
|
it('works with cached blocks', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate(
|
||||||
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}',
|
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
|
||||||
{ data: false }
|
)
|
||||||
);
|
.withCompileOptions({ data: false })
|
||||||
|
.withInput({
|
||||||
var result = template({
|
person: [
|
||||||
person: [
|
{ first: 'Alan', last: 'Johnson' },
|
||||||
{ first: 'Alan', last: 'Johnson' },
|
{ first: 'Alan', last: 'Johnson' }
|
||||||
{ first: 'Alan', last: 'Johnson' }
|
]
|
||||||
]
|
})
|
||||||
});
|
.toCompileTo('Alan JohnsonAlan Johnson');
|
||||||
equals(result, 'Alan JohnsonAlan Johnson');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inverted sections', function() {
|
describe('inverted sections', function() {
|
||||||
it('inverted sections with unset value', function() {
|
it('inverted sections with unset value', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
var hash = {};
|
)
|
||||||
shouldCompileTo(
|
.withMessage("Inverted section rendered when value isn't set.")
|
||||||
string,
|
.toCompileTo('Right On!');
|
||||||
hash,
|
|
||||||
'Right On!',
|
|
||||||
"Inverted section rendered when value isn't set."
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inverted section with false value', function() {
|
it('inverted section with false value', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
var hash = { goodbyes: false };
|
)
|
||||||
shouldCompileTo(
|
.withInput({ goodbyes: false })
|
||||||
string,
|
.withMessage('Inverted section rendered when value is false.')
|
||||||
hash,
|
.toCompileTo('Right On!');
|
||||||
'Right On!',
|
|
||||||
'Inverted section rendered when value is false.'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('inverted section with empty set', function() {
|
it('inverted section with empty set', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
var hash = { goodbyes: [] };
|
)
|
||||||
shouldCompileTo(
|
.withInput({ goodbyes: [] })
|
||||||
string,
|
.withMessage('Inverted section rendered when value is empty set.')
|
||||||
hash,
|
.toCompileTo('Right On!');
|
||||||
'Right On!',
|
|
||||||
'Inverted section rendered when value is empty set.'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block inverted sections', function() {
|
it('block inverted sections', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
|
.withInput({ none: 'No people' })
|
||||||
{ none: 'No people' },
|
.toCompileTo('No people');
|
||||||
'No people'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chained inverted sections', function() {
|
it('chained inverted sections', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}')
|
||||||
'{{#people}}{{name}}{{else if none}}{{none}}{{/people}}',
|
.withInput({ none: 'No people' })
|
||||||
{ none: 'No people' },
|
.toCompileTo('No people');
|
||||||
'No people'
|
|
||||||
);
|
expectTemplate(
|
||||||
shouldCompileTo(
|
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}'
|
||||||
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}',
|
)
|
||||||
{ none: 'No people' },
|
.withInput({ none: 'No people' })
|
||||||
'No people'
|
.toCompileTo('No people');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate(
|
||||||
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}',
|
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
|
||||||
{ none: 'No people' },
|
)
|
||||||
'No people'
|
.withInput({ none: 'No people' })
|
||||||
);
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chained inverted sections with mismatch', function() {
|
it('chained inverted sections with mismatch', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate(
|
||||||
shouldCompileTo(
|
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
|
||||||
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}',
|
).toThrow(Error);
|
||||||
{ none: 'No people' },
|
|
||||||
'No people'
|
|
||||||
);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block inverted sections with empty arrays', function() {
|
it('block inverted sections with empty arrays', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
'{{#people}}{{name}}{{^}}{{none}}{{/people}}',
|
.withInput({
|
||||||
{ none: 'No people', people: [] },
|
none: 'No people',
|
||||||
'No people'
|
people: []
|
||||||
);
|
})
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('standalone sections', function() {
|
describe('standalone sections', function() {
|
||||||
it('block standalone else sections', function() {
|
it('block standalone else sections', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
.withInput({ none: 'No people' })
|
||||||
{ none: 'No people' },
|
.toCompileTo('No people\n');
|
||||||
'No people\n'
|
|
||||||
);
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n')
|
||||||
shouldCompileTo(
|
.withInput({ none: 'No people' })
|
||||||
'{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n',
|
.toCompileTo('No people\n');
|
||||||
{ none: 'No people' },
|
|
||||||
'No people\n'
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
);
|
.withInput({ none: 'No people' })
|
||||||
shouldCompileTo(
|
.toCompileTo('No people\n');
|
||||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
|
||||||
{ none: 'No people' },
|
|
||||||
'No people\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block standalone else sections can be disabled', function() {
|
it('block standalone else sections can be disabled', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
.withInput({ none: 'No people' })
|
||||||
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
'\nNo people\n\n'
|
.toCompileTo('\nNo people\n\n');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
|
||||||
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
|
.withInput({ none: 'No people' })
|
||||||
[{ none: 'No people' }, {}, {}, { ignoreStandalone: true }],
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
'\nNo people\n\n'
|
.toCompileTo('\nNo people\n\n');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block standalone chained else sections', function() {
|
it('block standalone chained else sections', function() {
|
||||||
shouldCompileTo(
|
expectTemplate(
|
||||||
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n',
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n'
|
||||||
{ none: 'No people' },
|
)
|
||||||
'No people\n'
|
.withInput({ none: 'No people' })
|
||||||
);
|
.toCompileTo('No people\n');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n',
|
expectTemplate(
|
||||||
{ none: 'No people' },
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
|
||||||
'No people\n'
|
)
|
||||||
);
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nesting', function() {
|
it('should handle nesting', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
|
||||||
'{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.',
|
.withInput({
|
||||||
{ data: [1, 3, 5] },
|
data: [1, 3, 5]
|
||||||
'1\n3\n5\nOK.'
|
})
|
||||||
);
|
.toCompileTo('1\n3\n5\nOK.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compat mode', function() {
|
describe('compat mode', function() {
|
||||||
it('block with deep recursive lookup lookup', function() {
|
it('block with deep recursive lookup lookup', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
|
||||||
var hash = { omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
|
)
|
||||||
|
.withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
|
||||||
shouldCompileTo(
|
.withCompileOptions({ compat: true })
|
||||||
string,
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
[hash, undefined, undefined, true],
|
|
||||||
'Goodbye cruel OMG!'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('block with deep recursive pathed lookup', function() {
|
it('block with deep recursive pathed lookup', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
var hash = {
|
)
|
||||||
omg: { yes: 'OMG!' },
|
.withInput({
|
||||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
omg: { yes: 'OMG!' },
|
||||||
};
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
shouldCompileTo(
|
.withCompileOptions({ compat: true })
|
||||||
string,
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
[hash, undefined, undefined, true],
|
|
||||||
'Goodbye cruel OMG!'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('block with missed recursive lookup', function() {
|
|
||||||
var string =
|
|
||||||
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
|
||||||
var hash = {
|
|
||||||
omg: { no: 'OMG!' },
|
|
||||||
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
|
||||||
};
|
|
||||||
|
|
||||||
shouldCompileTo(
|
it('block with missed recursive lookup', function() {
|
||||||
string,
|
expectTemplate(
|
||||||
[hash, undefined, undefined, true],
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
'Goodbye cruel '
|
)
|
||||||
);
|
.withInput({
|
||||||
|
omg: { no: 'OMG!' },
|
||||||
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('decorators', function() {
|
describe('decorators', function() {
|
||||||
it('should apply mustache decorators', function() {
|
it('should apply mustache decorators', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn) {
|
|
||||||
fn.run = 'success';
|
fn.run = 'success';
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{*decorator}}{{/helper}}',
|
|
||||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply allow undefined return', function() {
|
it('should apply allow undefined return', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn() + options.fn.run;
|
return options.fn() + options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn) {
|
|
||||||
fn.run = 'cess';
|
fn.run = 'cess';
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{*decorator}}suc{{/helper}}',
|
|
||||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply block decorators', function() {
|
it('should apply block decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = options.fn();
|
fn.run = options.fn();
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
|
|
||||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support nested decorators', function() {
|
it('should support nested decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorators({
|
||||||
var decorators = {
|
decorator: function(fn, props, container, options) {
|
||||||
decorator: function(fn, props, container, options) {
|
fn.run = options.fn.nested + options.fn();
|
||||||
fn.run = options.fn.nested + options.fn();
|
return fn;
|
||||||
return fn;
|
},
|
||||||
},
|
nested: function(fn, props, container, options) {
|
||||||
nested: function(fn, props, container, options) {
|
props.nested = options.fn();
|
||||||
props.nested = options.fn();
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
|
|
||||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply multiple decorators', function() {
|
it('should apply multiple decorators', function() {
|
||||||
var helpers = {
|
expectTemplate(
|
||||||
helper: function(options) {
|
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = (fn.run || '') + options.fn();
|
fn.run = (fn.run || '') + options.fn();
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('success');
|
||||||
shouldCompileTo(
|
|
||||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
|
|
||||||
{ hash: {}, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should access parent variables', function() {
|
it('should access parent variables', function() {
|
||||||
var helpers = {
|
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||||
helper: function(options) {
|
.withHelper('helper', function(options) {
|
||||||
return options.fn.run;
|
return options.fn.run;
|
||||||
}
|
})
|
||||||
};
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
var decorators = {
|
|
||||||
decorator: function(fn, props, container, options) {
|
|
||||||
fn.run = options.args;
|
fn.run = options.args;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'success' })
|
||||||
shouldCompileTo(
|
.toCompileTo('success');
|
||||||
'{{#helper}}{{*decorator foo}}{{/helper}}',
|
|
||||||
{ hash: { foo: 'success' }, helpers: helpers, decorators: decorators },
|
|
||||||
'success'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with root program', function() {
|
it('should work with root program', function() {
|
||||||
var run;
|
var run;
|
||||||
var decorators = {
|
expectTemplate('{{*decorator "success"}}')
|
||||||
decorator: function(fn, props, container, options) {
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
equals(options.args[0], 'success');
|
equals(options.args[0], 'success');
|
||||||
run = true;
|
run = true;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'success' })
|
||||||
shouldCompileTo(
|
.toCompileTo('');
|
||||||
'{{*decorator "success"}}',
|
|
||||||
{ hash: { foo: 'success' }, decorators: decorators },
|
|
||||||
''
|
|
||||||
);
|
|
||||||
equals(run, true);
|
equals(run, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail when accessing variables from root', function() {
|
it('should fail when accessing variables from root', function() {
|
||||||
var run;
|
var run;
|
||||||
var decorators = {
|
expectTemplate('{{*decorator foo}}')
|
||||||
decorator: function(fn, props, container, options) {
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
equals(options.args[0], undefined);
|
equals(options.args[0], undefined);
|
||||||
run = true;
|
run = true;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ foo: 'fail' })
|
||||||
shouldCompileTo(
|
.toCompileTo('');
|
||||||
'{{*decorator foo}}',
|
|
||||||
{ hash: { foo: 'fail' }, decorators: decorators },
|
|
||||||
''
|
|
||||||
);
|
|
||||||
equals(run, true);
|
equals(run, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -481,6 +431,7 @@ describe('blocks', function() {
|
|||||||
equals(handlebarsEnv.decorators.foo, undefined);
|
equals(handlebarsEnv.decorators.foo, undefined);
|
||||||
equals(handlebarsEnv.decorators.bar, undefined);
|
equals(handlebarsEnv.decorators.bar, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails with multiple and args', function() {
|
it('fails with multiple and args', function() {
|
||||||
shouldThrow(
|
shouldThrow(
|
||||||
function() {
|
function() {
|
||||||
|
|||||||
+462
-449
File diff suppressed because it is too large
Load Diff
@@ -128,6 +128,115 @@ describe('compiler', function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createPathExpressionAST(depth, parts) {
|
||||||
|
return {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: depth,
|
||||||
|
parts: parts,
|
||||||
|
original: 'this'
|
||||||
|
},
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should safely handle AST with non-integer PathExpression depth', function() {
|
||||||
|
// depth '0' is coerced to 0 via Number(), compiles safely
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST('0', ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with negative PathExpression depth', function() {
|
||||||
|
// Negative depth is clamped to 0
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(-1, ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with fractional PathExpression depth', function() {
|
||||||
|
// Fractional depth is floored to an integer
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0.5, ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-array PathExpression parts', function() {
|
||||||
|
// Non-array parts are coerced to empty array, compiles safely
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0, 'this'))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-string PathExpression part', function() {
|
||||||
|
// Non-string parts are coerced to strings via String()
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0, [1]))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-boolean BooleanLiteral value type', function() {
|
||||||
|
// The compiler coerces BooleanLiteral.value via === true before
|
||||||
|
// emitting a pushLiteral opcode, so a non-boolean value like the
|
||||||
|
// string 'true' becomes the literal 'false'.
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 10 }
|
||||||
|
};
|
||||||
|
var result = Handlebars.compile({
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'BooleanLiteral',
|
||||||
|
value: 'true',
|
||||||
|
original: true,
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})();
|
||||||
|
// 'true' !== true, so the compiler emits pushLiteral('false').
|
||||||
|
// Handlebars does not render falsy values, so the output is empty.
|
||||||
|
expect(result).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore loc metadata in AST nodes', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile({
|
||||||
|
type: 'Program',
|
||||||
|
meta: null,
|
||||||
|
loc: { source: 'fake', start: { line: 1, column: 0 } },
|
||||||
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})(),
|
||||||
|
'Hello'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept AST with valid NumberLiteral values', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile(Handlebars.parse('{{lookup this 1}}'))(['a', 'b']),
|
||||||
|
'b'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept AST with valid BooleanLiteral values', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile(Handlebars.parse('{{#if true}}ok{{/if}}'))({}),
|
||||||
|
'ok'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can pass through an empty string', function() {
|
it('can pass through an empty string', function() {
|
||||||
equal(Handlebars.compile('')(), '');
|
equal(Handlebars.compile('')(), '');
|
||||||
});
|
});
|
||||||
|
|||||||
+157
-243
@@ -1,31 +1,24 @@
|
|||||||
describe('data', function() {
|
describe('data', function() {
|
||||||
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
||||||
var template = CompilerContext.compile('{{hello}}', { data: true });
|
expectTemplate('{{hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
|
||||||
return options.data.adjective + ' ' + this.noun;
|
return options.data.adjective + ' ' + this.noun;
|
||||||
}
|
})
|
||||||
};
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
|
.withInput({ noun: 'cat' })
|
||||||
var result = template(
|
.withMessage('Data output by helper')
|
||||||
{ noun: 'cat' },
|
.toCompileTo('happy cat');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('happy cat', result, 'Data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be looked up via @foo', function() {
|
it('data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{@hello}}');
|
expectTemplate('{{@hello}}')
|
||||||
var result = template({}, { data: { hello: 'hello' } });
|
.withRuntimeOptions({ data: { hello: 'hello' } })
|
||||||
equals('hello', result, '@foo retrieves template data');
|
.withMessage('@foo retrieves template data')
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deep @foo triggers automatic top-level data', function() {
|
it('deep @foo triggers automatic top-level data', function() {
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
|
|
||||||
);
|
|
||||||
|
|
||||||
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||||
|
|
||||||
helpers.let = function(options) {
|
helpers.let = function(options) {
|
||||||
@@ -39,124 +32,92 @@ describe('data', function() {
|
|||||||
return options.fn(this, { data: frame });
|
return options.fn(this, { data: frame });
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = template({ foo: true }, { helpers: helpers });
|
expectTemplate(
|
||||||
equals('Hello world', result, 'Automatic data was triggered');
|
'{{#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() {
|
it('parameter data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world}}');
|
expectTemplate('{{hello @world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
|
||||||
equals(
|
|
||||||
'Hello world',
|
|
||||||
result,
|
|
||||||
'@foo as a parameter retrieves template data'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hash values can be looked up via @foo', function() {
|
it('hash values can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile('{{hello noun=@world}}');
|
expectTemplate('{{hello noun=@world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(options) {
|
.withHelper('hello', function(options) {
|
||||||
return 'Hello ' + options.hash.noun;
|
return 'Hello ' + options.hash.noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template({}, { helpers: helpers, data: { world: 'world' } });
|
|
||||||
equals(
|
|
||||||
'Hello world',
|
|
||||||
result,
|
|
||||||
'@foo as a parameter retrieves template data'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested parameter data can be looked up via @foo.bar', function() {
|
it('nested parameter data can be looked up via @foo.bar', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template(
|
|
||||||
{},
|
|
||||||
{ helpers: helpers, data: { world: { bar: 'world' } } }
|
|
||||||
);
|
|
||||||
equals(
|
|
||||||
'Hello world',
|
|
||||||
result,
|
|
||||||
'@foo as a parameter retrieves template data'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested parameter data does not fail with @world.bar', function() {
|
it('nested parameter data does not fail with @world.bar', function() {
|
||||||
var template = CompilerContext.compile('{{hello @world.bar}}');
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { foo: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return 'Hello ' + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello undefined');
|
||||||
var result = template(
|
|
||||||
{},
|
|
||||||
{ helpers: helpers, data: { foo: { bar: 'world' } } }
|
|
||||||
);
|
|
||||||
equals(
|
|
||||||
'Hello undefined',
|
|
||||||
result,
|
|
||||||
'@foo as a parameter retrieves template data'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parameter data throws when using complex scope references', function() {
|
it('parameter data throws when using complex scope references', function() {
|
||||||
var string = '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}';
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be functions', function() {
|
it('data can be functions', function() {
|
||||||
var template = CompilerContext.compile('{{@hello}}');
|
expectTemplate('{{@hello}}')
|
||||||
var result = template(
|
.withRuntimeOptions({
|
||||||
{},
|
|
||||||
{
|
|
||||||
data: {
|
data: {
|
||||||
hello: function() {
|
hello: function() {
|
||||||
return 'hello';
|
return 'hello';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
.toCompileTo('hello');
|
||||||
equals('hello', result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data can be functions with params', function() {
|
it('data can be functions with params', function() {
|
||||||
var template = CompilerContext.compile('{{@hello "hello"}}');
|
expectTemplate('{{@hello "hello"}}')
|
||||||
var result = template(
|
.withRuntimeOptions({
|
||||||
{},
|
|
||||||
{
|
|
||||||
data: {
|
data: {
|
||||||
hello: function(arg) {
|
hello: function(arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
.toCompileTo('hello');
|
||||||
equals('hello', result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data is inherited downstream', function() {
|
it('data is inherited downstream', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate(
|
||||||
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}',
|
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'
|
||||||
{ data: true }
|
)
|
||||||
);
|
.withInput({ bar: { baz: 'hello world' } })
|
||||||
var helpers = {
|
.withCompileOptions({ data: true })
|
||||||
let: function(options) {
|
.withHelper('let', function(options) {
|
||||||
var frame = Handlebars.createFrame(options.data);
|
var frame = Handlebars.createFrame(options.data);
|
||||||
for (var prop in options.hash) {
|
for (var prop in options.hash) {
|
||||||
if (prop in options.hash) {
|
if (prop in options.hash) {
|
||||||
@@ -164,201 +125,154 @@ describe('data', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options.fn(this, { data: frame });
|
return options.fn(this, { data: frame });
|
||||||
}
|
})
|
||||||
};
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.withMessage('data variables are inherited downstream')
|
||||||
var result = template(
|
.toCompileTo('2hello world1');
|
||||||
{ bar: { baz: 'hello world' } },
|
|
||||||
{ helpers: helpers, data: {} }
|
|
||||||
);
|
|
||||||
equals('2hello world1', result, 'data variables are inherited downstream');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
||||||
var template = CompilerContext.compile('{{>myPartial}}', { data: true });
|
expectTemplate('{{>myPartial}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var partials = {
|
.withPartial('myPartial', '{{hello}}')
|
||||||
myPartial: CompilerContext.compile('{{hello}}', { data: true })
|
.withHelper('hello', function(options) {
|
||||||
};
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.data.adjective + ' ' + this.noun;
|
return options.data.adjective + ' ' + this.noun;
|
||||||
}
|
})
|
||||||
};
|
.withInput({ noun: 'cat' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Data output by helper inside partial')
|
||||||
{ noun: 'cat' },
|
.toCompileTo('happy cat');
|
||||||
{ helpers: helpers, partials: partials, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('happy cat', result, 'Data output by helper inside partial');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
|
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 });
|
expectTemplate('{{hello world}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(noun, options) {
|
||||||
hello: function(noun, options) {
|
|
||||||
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, world: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Data output by helper')
|
||||||
{ exclaim: true, world: 'world' },
|
.toCompileTo('happy world!');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('happy world!', result, 'Data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
||||||
var template = CompilerContext.compile('{{#hello}}{{world}}{{/hello}}', {
|
expectTemplate('{{#hello}}{{world}}{{/hello}}')
|
||||||
data: true
|
.withCompileOptions({
|
||||||
});
|
data: true
|
||||||
|
})
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
|
||||||
return options.fn(this);
|
return options.fn(this);
|
||||||
},
|
})
|
||||||
world: function(options) {
|
.withHelper('world', function(options) {
|
||||||
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Data output by helper')
|
||||||
{ exclaim: true },
|
.toCompileTo('happy world!');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('happy world!', result, 'Data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
.withCompileOptions({ data: true })
|
||||||
{ data: true }
|
.withHelper('hello', function(options) {
|
||||||
);
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.fn({ exclaim: '?' });
|
return options.fn({ exclaim: '?' });
|
||||||
},
|
})
|
||||||
world: function(thing, options) {
|
.withHelper('world', function(thing, options) {
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Data output by helper')
|
||||||
{ exclaim: true, zomg: 'world' },
|
.toCompileTo('happy world?');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('happy world?', result, 'Data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
|
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(
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
.withCompileOptions({ data: true })
|
||||||
{ data: true }
|
.withHelper('hello', function(options) {
|
||||||
);
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
|
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
|
||||||
},
|
})
|
||||||
world: function(thing, options) {
|
.withHelper('world', function(thing, options) {
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
|
||||||
var result = template(
|
.withMessage('Data output by helper')
|
||||||
{ exclaim: true, zomg: 'world' },
|
.toCompileTo('#win happy world?');
|
||||||
{ helpers: helpers, data: { adjective: 'happy', accessData: '#win' } }
|
|
||||||
);
|
|
||||||
equals('#win happy world?', result, 'Data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('you can override inherited data when invoking a helper', function() {
|
it('you can override inherited data when invoking a helper', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
|
||||||
'{{#hello}}{{world zomg}}{{/hello}}',
|
.withCompileOptions({ data: true })
|
||||||
{ data: true }
|
.withHelper('hello', function(options) {
|
||||||
);
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.fn(
|
return options.fn(
|
||||||
{ exclaim: '?', zomg: 'world' },
|
{ exclaim: '?', zomg: 'world' },
|
||||||
{ data: { adjective: 'sad' } }
|
{ data: { adjective: 'sad' } }
|
||||||
);
|
);
|
||||||
},
|
})
|
||||||
world: function(thing, options) {
|
.withHelper('world', function(thing, options) {
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'planet' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Overriden data output by helper')
|
||||||
{ exclaim: true, zomg: 'planet' },
|
.toCompileTo('sad world?');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('sad world?', result, 'Overriden data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('you can override inherited data when invoking a helper with depth', function() {
|
it('you can override inherited data when invoking a helper with depth', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
'{{#hello}}{{world ../zomg}}{{/hello}}',
|
.withCompileOptions({ data: true })
|
||||||
{ data: true }
|
.withHelper('hello', function(options) {
|
||||||
);
|
|
||||||
|
|
||||||
var helpers = {
|
|
||||||
hello: function(options) {
|
|
||||||
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
|
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
|
||||||
},
|
})
|
||||||
world: function(thing, options) {
|
.withHelper('world', function(thing, options) {
|
||||||
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template(
|
.withMessage('Overriden data output by helper')
|
||||||
{ exclaim: true, zomg: 'world' },
|
.toCompileTo('sad world?');
|
||||||
{ helpers: helpers, data: { adjective: 'happy' } }
|
|
||||||
);
|
|
||||||
equals('sad world?', result, 'Overriden data output by helper');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('@root', function() {
|
describe('@root', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({ foo: 'hello' }, { data: {} });
|
.withInput({ foo: 'hello' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.toCompileTo('hello');
|
||||||
|
|
||||||
result = template({ foo: 'hello' }, {});
|
expectTemplate('{{@root.foo}}')
|
||||||
equals('hello', result);
|
.withInput({ foo: 'hello' })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passed root values take priority', function() {
|
it('passed root values take priority', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({}, { data: { root: { foo: 'hello' } } });
|
.withInput({ foo: 'should not be used' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: { root: { foo: 'hello' } } })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nesting', function() {
|
describe('nesting', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate(
|
||||||
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
|
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
|
||||||
);
|
)
|
||||||
var result = template(
|
.withInput({ foo: 'hello' })
|
||||||
{ foo: 'hello' },
|
.withHelper('helper', function(options) {
|
||||||
{
|
var frame = Handlebars.createFrame(options.data);
|
||||||
helpers: {
|
frame.depth = options.data.depth + 1;
|
||||||
helper: function(options) {
|
return options.fn(this, { data: frame });
|
||||||
var frame = Handlebars.createFrame(options.data);
|
})
|
||||||
frame.depth = options.data.depth + 1;
|
.withRuntimeOptions({
|
||||||
return options.fn(this, { data: frame });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: {
|
data: {
|
||||||
depth: 0
|
depth: 0
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
);
|
.toCompileTo('2 1 0');
|
||||||
equals('2 1 0', result);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+40
-7
@@ -129,6 +129,7 @@ function HandlebarsTestBench(templateAsString) {
|
|||||||
this.templateAsString = templateAsString;
|
this.templateAsString = templateAsString;
|
||||||
this.helpers = {};
|
this.helpers = {};
|
||||||
this.partials = {};
|
this.partials = {};
|
||||||
|
this.decorators = {};
|
||||||
this.input = {};
|
this.input = {};
|
||||||
this.message =
|
this.message =
|
||||||
'Template' + templateAsString + ' does not evaluate to expected output';
|
'Template' + templateAsString + ' does not evaluate to expected output';
|
||||||
@@ -146,11 +147,43 @@ HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
|
|||||||
return this;
|
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) {
|
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
|
||||||
this.partials[name] = partialAsString;
|
this.partials[name] = partialAsString;
|
||||||
return this;
|
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) {
|
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
|
||||||
this.compileOptions = compileOptions;
|
this.compileOptions = compileOptions;
|
||||||
return this;
|
return this;
|
||||||
@@ -167,19 +200,18 @@ HandlebarsTestBench.prototype.withMessage = function(message) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
|
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)
|
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||||
HandlebarsTestBench.prototype.toThrow = function(
|
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
|
||||||
errorLike,
|
|
||||||
errMsgMatcher,
|
|
||||||
msg
|
|
||||||
) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
expect(function() {
|
expect(function() {
|
||||||
self._compileAndExecute();
|
self._compileAndExecute();
|
||||||
}).to.throw(errorLike, errMsgMatcher, msg);
|
}).to.throw(errorLike, errMsgMatcher, this.message);
|
||||||
};
|
};
|
||||||
|
|
||||||
HandlebarsTestBench.prototype._compileAndExecute = function() {
|
HandlebarsTestBench.prototype._compileAndExecute = function() {
|
||||||
@@ -202,5 +234,6 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
|
|||||||
});
|
});
|
||||||
combinedRuntimeOptions.helpers = this.helpers;
|
combinedRuntimeOptions.helpers = this.helpers;
|
||||||
combinedRuntimeOptions.partials = this.partials;
|
combinedRuntimeOptions.partials = this.partials;
|
||||||
|
combinedRuntimeOptions.decorators = this.decorators;
|
||||||
return combinedRuntimeOptions;
|
return combinedRuntimeOptions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["bom"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "a";
|
return "a";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
})();
|
})();
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
templates['firstTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["firstTemplate"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>1</div>";
|
return "<div>1</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['secondTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["secondTemplate"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>2</div>";
|
return "<div>2</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
return templates;
|
return templates;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['artifacts/partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["artifacts/partial.template"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>Test Partial</div>";
|
return "<div>Test Partial</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['some-path/handlebars.runtime'], function(Handlebars) {
|
define(["some-path/handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
return templates;
|
return templates;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['non.default.extension'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["non.default.extension"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>This is a test</div>";
|
return "<div>This is a test</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['known.helpers'] = template({"1":function(container,depth0,helpers,partials,data) {
|
return templates["known.helpers"] = template({"0":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
return parent[propertyName];
|
return parent[propertyName];
|
||||||
@@ -8,8 +8,8 @@ return parent[propertyName];
|
|||||||
return undefined
|
return undefined
|
||||||
};
|
};
|
||||||
return " <div>Some known helper</div>\n"
|
return " <div>Some known helper</div>\n"
|
||||||
+ ((stack1 = lookupProperty(helpers,"anotherHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"anotherHelper","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":22}}})) != null ? stack1 : "");
|
+ ((stack1 = lookupProperty(helpers,"anotherHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"anotherHelper","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":22}}})) != null ? stack1 : "");
|
||||||
},"2":function(container,depth0,helpers,partials,data) {
|
},"1":function(container,depth0,helpers,partials,data) {
|
||||||
return " <div>Another known helper</div>\n";
|
return " <div>Another known helper</div>\n";
|
||||||
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
@@ -18,7 +18,7 @@ return parent[propertyName];
|
|||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
};
|
};
|
||||||
return ((stack1 = lookupProperty(helpers,"someHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"someHelper","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":15}}})) != null ? stack1 : "");
|
return ((stack1 = lookupProperty(helpers,"someHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"someHelper","hash":{},"fn":container.program(0, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":15}}})) != null ? stack1 : "");
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return Handlebars.partials['partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return Handlebars.partials["partial.template"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>Test Partial</div>";
|
return "<div>Test Partial</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
+527
-822
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
@@ -20,14 +20,18 @@ describe('javascript-compiler api', function() {
|
|||||||
return parent + '.bar_' + name;
|
return parent + '.bar_' + name;
|
||||||
};
|
};
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ bar_foo: 'food' })
|
||||||
|
.toCompileTo('food');
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
||||||
// to avoid errors in older browsers.
|
// to avoid errors in older browsers.
|
||||||
it('should handle reserved words', function() {
|
it('should handle reserved words', function() {
|
||||||
shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food');
|
expectTemplate('{{foo}} {{~null~}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('food');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#compilerInfo', function() {
|
describe('#compilerInfo', function() {
|
||||||
@@ -49,7 +53,9 @@ describe('javascript-compiler api', function() {
|
|||||||
throw new Error("It didn't work");
|
throw new Error("It didn't work");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
|
expectTemplate('{{foo}} ')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('food ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('buffer', function() {
|
describe('buffer', function() {
|
||||||
@@ -70,7 +76,9 @@ describe('javascript-compiler api', function() {
|
|||||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
|
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
|
||||||
return this.quotedString('foo_');
|
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() {
|
it('should allow append buffer override', function() {
|
||||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
|
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
|
||||||
@@ -78,7 +86,9 @@ describe('javascript-compiler api', function() {
|
|||||||
) {
|
) {
|
||||||
return $superAppend.call(this, [string, ' + "_foo"']);
|
return $superAppend.call(this, [string, ' + "_foo"']);
|
||||||
};
|
};
|
||||||
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('food_foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+442
-606
File diff suppressed because it is too large
Load Diff
+82
-1
@@ -182,7 +182,7 @@ describe('precompiler', function() {
|
|||||||
return 'amd';
|
return 'amd';
|
||||||
};
|
};
|
||||||
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
||||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
equal(/return Handlebars\.partials\["empty"\]/.test(log), true);
|
||||||
equal(/template\(amd\)/.test(log), true);
|
equal(/template\(amd\)/.test(log), true);
|
||||||
});
|
});
|
||||||
it('should output multiple amd partials', function() {
|
it('should output multiple amd partials', function() {
|
||||||
@@ -405,4 +405,85 @@ describe('precompiler', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GHSA-xjpj-3mr7-gcpf: precompiler output escaping', function() {
|
||||||
|
var FullHandlebars = require('../dist/cjs/handlebars')['default'];
|
||||||
|
|
||||||
|
function runCliAndCaptureOutput(options) {
|
||||||
|
var output = '';
|
||||||
|
var oldLog = console.log;
|
||||||
|
console.log = function() {
|
||||||
|
output += Array.prototype.join.call(arguments, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Precompiler.cli(options);
|
||||||
|
} finally {
|
||||||
|
console.log = oldLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not inject raw template names into generated code', function() {
|
||||||
|
var output = runCliAndCaptureOutput({
|
||||||
|
templates: [
|
||||||
|
{
|
||||||
|
name: "evil'];global.__xjpjName=1;//",
|
||||||
|
source: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
amd: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).to.not.match(/\['evil'\];global\.__xjpjName=1/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not inject raw commonjs option values into generated code', function() {
|
||||||
|
var output = runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
commonjs: 'handlebars");global.__xjpjCommon=1;//'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).to.not.match(
|
||||||
|
/require\("handlebars"\);global\.__xjpjCommon=1/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid namespace expressions', function() {
|
||||||
|
expect(function() {
|
||||||
|
runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
namespace: 'App.ns;global.__xjpjNamespace=1;//'
|
||||||
|
});
|
||||||
|
}).to.throw(/Invalid namespace/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize sourceMappingURL comment values', function() {
|
||||||
|
var oldPrecompile = FullHandlebars.precompile;
|
||||||
|
var oldWriteFileSync = fs.writeFileSync;
|
||||||
|
FullHandlebars.precompile = function() {
|
||||||
|
return {
|
||||||
|
code: '""',
|
||||||
|
map: '{"version":3,"sources":[],"names":[],"mappings":""}'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
fs.writeFileSync = function() {};
|
||||||
|
|
||||||
|
var output;
|
||||||
|
try {
|
||||||
|
output = runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
map: 'good.js.map\n;global.__xjpjMap=1;//'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
FullHandlebars.precompile = oldPrecompile;
|
||||||
|
fs.writeFileSync = oldWriteFileSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(output).to.not.match(
|
||||||
|
/sourceMappingURL=[^\n]*\n;global\.__xjpjMap=1/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+217
-244
@@ -1,61 +1,53 @@
|
|||||||
describe('Regressions', function() {
|
describe('Regressions', function() {
|
||||||
it('GH-94: Cannot read property of undefined', function() {
|
it('GH-94: Cannot read property of undefined', function() {
|
||||||
var data = {
|
expectTemplate('{{#books}}{{title}}{{author.name}}{{/books}}')
|
||||||
books: [
|
.withInput({
|
||||||
{
|
books: [
|
||||||
title: 'The origin of species',
|
{
|
||||||
author: {
|
title: 'The origin of species',
|
||||||
name: 'Charles Darwin'
|
author: {
|
||||||
|
name: 'Charles Darwin'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Lazarillo de Tormes'
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
})
|
||||||
title: 'Lazarillo de Tormes'
|
.withMessage('Renders without an undefined property error')
|
||||||
}
|
.toCompileTo('The origin of speciesCharles DarwinLazarillo de Tormes');
|
||||||
]
|
|
||||||
};
|
|
||||||
var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
|
|
||||||
shouldCompileTo(
|
|
||||||
string,
|
|
||||||
data,
|
|
||||||
'The origin of speciesCharles DarwinLazarillo de Tormes',
|
|
||||||
'Renders without an undefined property error'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("GH-150: Inverted sections print when they shouldn't", function() {
|
it("GH-150: Inverted sections print when they shouldn't", function() {
|
||||||
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
|
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate(string)
|
||||||
string,
|
.withMessage(
|
||||||
{},
|
"inverted sections run when property isn't present in context"
|
||||||
'not set :: ',
|
)
|
||||||
"inverted sections run when property isn't present in context"
|
.toCompileTo('not set :: ');
|
||||||
);
|
|
||||||
shouldCompileTo(
|
expectTemplate(string)
|
||||||
string,
|
.withInput({ set: undefined })
|
||||||
{ set: undefined },
|
.withMessage('inverted sections run when property is undefined')
|
||||||
'not set :: ',
|
.toCompileTo('not set :: ');
|
||||||
'inverted sections run when property is undefined'
|
|
||||||
);
|
expectTemplate(string)
|
||||||
shouldCompileTo(
|
.withInput({ set: false })
|
||||||
string,
|
.withMessage('inverted sections run when property is false')
|
||||||
{ set: false },
|
.toCompileTo('not set :: ');
|
||||||
'not set :: ',
|
|
||||||
'inverted sections run when property is false'
|
expectTemplate(string)
|
||||||
);
|
.withInput({ set: true })
|
||||||
shouldCompileTo(
|
.withMessage("inverted sections don't run when property is true")
|
||||||
string,
|
.toCompileTo(' :: set');
|
||||||
{ set: true },
|
|
||||||
' :: set',
|
|
||||||
"inverted sections don't run when property is true"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-158: Using array index twice, breaks the template', function() {
|
it('GH-158: Using array index twice, breaks the template', function() {
|
||||||
var string = '{{arr.[0]}}, {{arr.[1]}}';
|
expectTemplate('{{arr.[0]}}, {{arr.[1]}}')
|
||||||
var data = { arr: [1, 2] };
|
.withInput({ arr: [1, 2] })
|
||||||
|
.withMessage('it works as expected')
|
||||||
shouldCompileTo(string, data, '1, 2', 'it works as expected');
|
.toCompileTo('1, 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
|
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
|
||||||
@@ -73,6 +65,7 @@ describe('Regressions', function() {
|
|||||||
'\n' +
|
'\n' +
|
||||||
'<small>Nothing to check out...</small>\n' +
|
'<small>Nothing to check out...</small>\n' +
|
||||||
'{{/hasThings}}';
|
'{{/hasThings}}';
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
thing: function() {
|
thing: function() {
|
||||||
return 'blah';
|
return 'blah';
|
||||||
@@ -95,25 +88,22 @@ describe('Regressions', function() {
|
|||||||
'<li class=two>@dhg</li>\n' +
|
'<li class=two>@dhg</li>\n' +
|
||||||
'<li class=three>@sayrer</li>\n' +
|
'<li class=three>@sayrer</li>\n' +
|
||||||
'</ul>.\n';
|
'</ul>.\n';
|
||||||
shouldCompileTo(string, data, output);
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput(data)
|
||||||
|
.toCompileTo(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-408: Multiple loops fail', function() {
|
it('GH-408: Multiple loops fail', function() {
|
||||||
var context = [
|
expectTemplate(
|
||||||
{ name: 'John Doe', location: { city: 'Chicago' } },
|
|
||||||
{ name: 'Jane Doe', location: { city: 'New York' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
|
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
|
||||||
);
|
)
|
||||||
|
.withInput([
|
||||||
var result = template(context);
|
{ name: 'John Doe', location: { city: 'Chicago' } },
|
||||||
equals(
|
{ name: 'Jane Doe', location: { city: 'New York' } }
|
||||||
result,
|
])
|
||||||
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
|
.withMessage('It should output multiple times')
|
||||||
'It should output multiple times'
|
.toCompileTo('John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe');
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GS-428: Nested if else rendering', function() {
|
it('GS-428: Nested if else rendering', function() {
|
||||||
@@ -131,259 +121,242 @@ describe('Regressions', function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
|
expectTemplate(succeedingTemplate)
|
||||||
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
|
.withHelpers(helpers)
|
||||||
|
.toCompileTo(' Expected ');
|
||||||
|
|
||||||
|
expectTemplate(failingTemplate)
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.toCompileTo(' Expected ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-458: Scoped this identifier', function() {
|
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() {
|
it('GH-375: Unicode line terminators', function() {
|
||||||
shouldCompileTo('\u2028', {}, '\u2028');
|
expectTemplate('\u2028').toCompileTo('\u2028');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-534: Object prototype aliases', function() {
|
it('GH-534: Object prototype aliases', function() {
|
||||||
/* eslint-disable no-extend-native */
|
/* eslint-disable no-extend-native */
|
||||||
Object.prototype[0xd834] = true;
|
Object.prototype[0xd834] = true;
|
||||||
|
|
||||||
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ foo: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
delete Object.prototype[0xd834];
|
delete Object.prototype[0xd834];
|
||||||
/* eslint-enable no-extend-native */
|
/* eslint-enable no-extend-native */
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-437: Matching escaping', function() {
|
it('GH-437: Matching escaping', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{{a}}').toThrow(Error, /Parse error on/);
|
||||||
CompilerContext.compile('{{{a}}');
|
expectTemplate('{{a}}}').toThrow(Error, /Parse error on/);
|
||||||
}, Error);
|
|
||||||
shouldThrow(function() {
|
|
||||||
CompilerContext.compile('{{a}}}');
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-676: Using array in escaping mustache fails', function() {
|
it('GH-676: Using array in escaping mustache fails', function() {
|
||||||
var string = '{{arr}}';
|
|
||||||
var data = { arr: [1, 2] };
|
var data = { arr: [1, 2] };
|
||||||
|
|
||||||
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
|
expectTemplate('{{arr}}')
|
||||||
|
.withInput(data)
|
||||||
|
.withMessage('it works as expected')
|
||||||
|
.toCompileTo(data.arr.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Mustache man page', function() {
|
it('Mustache man page', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
|
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}'
|
||||||
var data = {
|
)
|
||||||
name: 'Chris',
|
.withInput({
|
||||||
value: 10000,
|
name: 'Chris',
|
||||||
taxed_value: 10000 - 10000 * 0.4,
|
value: 10000,
|
||||||
in_ca: true
|
taxed_value: 10000 - 10000 * 0.4,
|
||||||
};
|
in_ca: true
|
||||||
|
})
|
||||||
shouldCompileTo(
|
.withMessage('the hello world mustache example works')
|
||||||
string,
|
.toCompileTo(
|
||||||
data,
|
'Hello Chris. You have just won $10000! Well, $6000, after taxes.'
|
||||||
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
|
);
|
||||||
'the hello world mustache example works'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-731: zero context rendering', function() {
|
it('GH-731: zero context rendering', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{#foo}} This is {{bar}} ~ {{/foo}}')
|
||||||
'{{#foo}} This is {{bar}} ~ {{/foo}}',
|
.withInput({
|
||||||
{ foo: 0, bar: 'OK' },
|
foo: 0,
|
||||||
' This is ~ '
|
bar: 'OK'
|
||||||
);
|
})
|
||||||
|
.toCompileTo(' This is ~ ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-820: zero pathed rendering', function() {
|
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() {
|
it('GH-837: undefined values for helpers', function() {
|
||||||
var helpers = {
|
expectTemplate('{{str bar.baz}}')
|
||||||
str: function(value) {
|
.withHelpers({
|
||||||
return value + '';
|
str: function(value) {
|
||||||
}
|
return value + '';
|
||||||
};
|
}
|
||||||
|
})
|
||||||
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
|
.toCompileTo('undefined');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-926: Depths and de-dupe', function() {
|
it('GH-926: Depths and de-dupe', function() {
|
||||||
var context = {
|
expectTemplate(
|
||||||
name: 'foo',
|
|
||||||
data: [1],
|
|
||||||
notData: [1]
|
|
||||||
};
|
|
||||||
|
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
|
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
|
||||||
);
|
)
|
||||||
|
.withInput({
|
||||||
var result = template(context);
|
name: 'foo',
|
||||||
equals(result, 'foo');
|
data: [1],
|
||||||
|
notData: [1]
|
||||||
|
})
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1021: Each empty string key', function() {
|
it('GH-1021: Each empty string key', function() {
|
||||||
var data = {
|
expectTemplate('{{#each data}}Key: {{@key}}\n{{/each}}')
|
||||||
'': 'foo',
|
.withInput({
|
||||||
name: 'Chris',
|
data: {
|
||||||
value: 10000
|
'': 'foo',
|
||||||
};
|
name: 'Chris',
|
||||||
|
value: 10000
|
||||||
shouldCompileTo(
|
}
|
||||||
'{{#each data}}Key: {{@key}}\n{{/each}}',
|
})
|
||||||
{ data: data },
|
.toCompileTo('Key: \nKey: name\nKey: value\n');
|
||||||
'Key: \nKey: name\nKey: value\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1054: Should handle simple safe string responses', function() {
|
it('GH-1054: Should handle simple safe string responses', function() {
|
||||||
var root = '{{#wrap}}{{>partial}}{{/wrap}}';
|
expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}')
|
||||||
var partials = {
|
.withHelpers({
|
||||||
partial: '{{#wrap}}<partial>{{/wrap}}'
|
wrap: function(options) {
|
||||||
};
|
return new Handlebars.SafeString(options.fn());
|
||||||
var helpers = {
|
}
|
||||||
wrap: function(options) {
|
})
|
||||||
return new Handlebars.SafeString(options.fn());
|
.withPartials({
|
||||||
}
|
partial: '{{#wrap}}<partial>{{/wrap}}'
|
||||||
};
|
})
|
||||||
|
.toCompileTo('<partial>');
|
||||||
shouldCompileToWithPartials(
|
|
||||||
root,
|
|
||||||
[{}, helpers, partials],
|
|
||||||
true,
|
|
||||||
'<partial>'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1065: Sparse arrays', function() {
|
it('GH-1065: Sparse arrays', function() {
|
||||||
var array = [];
|
var array = [];
|
||||||
array[1] = 'foo';
|
array[1] = 'foo';
|
||||||
array[3] = 'bar';
|
array[3] = 'bar';
|
||||||
shouldCompileTo(
|
expectTemplate('{{#each array}}{{@index}}{{.}}{{/each}}')
|
||||||
'{{#each array}}{{@index}}{{.}}{{/each}}',
|
.withInput({ array: array })
|
||||||
{ array: array },
|
.toCompileTo('1foo3bar');
|
||||||
'1foo3bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1093: Undefined helper context', function() {
|
it('GH-1093: Undefined helper context', function() {
|
||||||
var obj = { foo: undefined, bar: 'bat' };
|
expectTemplate('{{#each obj}}{{{helper}}}{{.}}{{/each}}')
|
||||||
var helpers = {
|
.withInput({ obj: { foo: undefined, bar: 'bat' } })
|
||||||
helper: function() {
|
.withHelpers({
|
||||||
// It's valid to execute a block against an undefined context, but
|
helper: function() {
|
||||||
// helpers can not do so, so we expect to have an empty object here;
|
// It's valid to execute a block against an undefined context, but
|
||||||
for (var name in this) {
|
// helpers can not do so, so we expect to have an empty object here;
|
||||||
if (Object.prototype.hasOwnProperty.call(this, name)) {
|
for (var name in this) {
|
||||||
return 'found';
|
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';
|
.toCompileTo('notfoundbat');
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
shouldCompileTo(
|
|
||||||
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
|
|
||||||
[{ obj: obj }, helpers],
|
|
||||||
'notfoundbat'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support multiple levels of inline partials', function() {
|
it('should support multiple levels of inline partials', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
|
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}'
|
||||||
var partials = {
|
)
|
||||||
doctype: 'doctype{{> content}}',
|
.withPartials({
|
||||||
layout:
|
doctype: 'doctype{{> content}}',
|
||||||
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
|
layout:
|
||||||
};
|
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
|
||||||
shouldCompileToWithPartials(
|
})
|
||||||
string,
|
.toCompileTo('doctypelayoutsubcontent');
|
||||||
[{}, {}, partials],
|
|
||||||
true,
|
|
||||||
'doctypelayoutsubcontent'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
|
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
|
||||||
var string = '{{#> layout}}{{/layout}}';
|
expectTemplate('{{#> layout}}{{/layout}}')
|
||||||
var partials = {
|
.withPartials({
|
||||||
doctype: 'doctype{{> content}}',
|
doctype: 'doctype{{> content}}',
|
||||||
layout:
|
layout:
|
||||||
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
|
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
|
||||||
};
|
})
|
||||||
shouldCompileToWithPartials(
|
.toCompileTo('doctypelayoutsubcontent');
|
||||||
string,
|
|
||||||
[{}, {}, partials],
|
|
||||||
true,
|
|
||||||
'doctypelayoutsubcontent'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
|
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
|
||||||
var string = '{{#> layout}}Outer{{/layout}}';
|
expectTemplate('{{#> layout}}Outer{{/layout}}')
|
||||||
var partials = {
|
.withPartials({
|
||||||
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
|
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
|
||||||
inner: ''
|
inner: ''
|
||||||
};
|
})
|
||||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
|
.toCompileTo('Outer');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1135 : Context handling within each iteration', function() {
|
it('GH-1135 : Context handling within each iteration', function() {
|
||||||
var obj = { array: [1], name: 'John' };
|
expectTemplate(
|
||||||
var helpers = {
|
|
||||||
myif: function(conditional, options) {
|
|
||||||
if (conditional) {
|
|
||||||
return options.fn(this);
|
|
||||||
} else {
|
|
||||||
return options.inverse(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
shouldCompileTo(
|
|
||||||
'{{#each array}}\n' +
|
'{{#each array}}\n' +
|
||||||
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
|
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
|
||||||
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
|
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
|
||||||
'{{/each}}',
|
'{{/each}}'
|
||||||
[obj, helpers],
|
)
|
||||||
' 1. IF: John--\n' + ' 2. MYIF: John==\n'
|
.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() {
|
it('GH-1186: Support block params for existing programs', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
|
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
|
||||||
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
|
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
|
||||||
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
|
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}'
|
||||||
|
)
|
||||||
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
|
.withInput({
|
||||||
|
listOne: ['a'],
|
||||||
|
listTwo: ['b']
|
||||||
|
})
|
||||||
|
.withMessage('')
|
||||||
|
.toCompileTo('ab');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
|
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
|
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}'
|
||||||
shouldCompileTo(
|
)
|
||||||
string,
|
.withInput({
|
||||||
{ value: 'parent', list: [null, 'a'] },
|
value: 'parent',
|
||||||
'parent=parent parent=parent ',
|
list: [null, 'a']
|
||||||
''
|
})
|
||||||
);
|
.withMessage('')
|
||||||
|
.toCompileTo('parent=parent parent=parent ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
|
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
|
||||||
var string = 'template {{>partial}} template';
|
expectTemplate('template {{>partial}} template')
|
||||||
var partials = {
|
.withPartials({
|
||||||
partialWithBlock:
|
partialWithBlock:
|
||||||
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
|
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
|
||||||
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
|
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
|
||||||
};
|
})
|
||||||
shouldCompileToWithPartials(
|
.toCompileTo('template block partial block template');
|
||||||
string,
|
|
||||||
[{}, {}, partials],
|
|
||||||
true,
|
|
||||||
'template block partial block template'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
|
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
|
||||||
@@ -482,14 +455,14 @@ describe('Regressions', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow hash with protected array names', function() {
|
it('should allow hash with protected array names', function() {
|
||||||
var obj = { array: [1], name: 'John' };
|
expectTemplate('{{helpa length="foo"}}')
|
||||||
var helpers = {
|
.withInput({ array: [1], name: 'John' })
|
||||||
helpa: function(options) {
|
.withHelpers({
|
||||||
return options.hash.length;
|
helpa: function(options) {
|
||||||
}
|
return options.hash.length;
|
||||||
};
|
}
|
||||||
|
})
|
||||||
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
|
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
|
||||||
|
|||||||
+10
-3
@@ -54,6 +54,13 @@ describe('runtime', function() {
|
|||||||
/Template was precompiled with an older version of Handlebars than the current runtime/
|
/Template was precompiled with an older version of Handlebars than the current runtime/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should safely resolve missing partial map entries', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.VM.resolvePartial(undefined, {}, { name: 'missing' }),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#child', function() {
|
describe('#child', function() {
|
||||||
@@ -91,13 +98,13 @@ describe('runtime', function() {
|
|||||||
it('should expose child template', function() {
|
it('should expose child template', function() {
|
||||||
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
||||||
// Calling twice to hit the non-compiled case.
|
// Calling twice to hit the non-compiled case.
|
||||||
equal(template._child(1)(), 'bar');
|
equal(template._child(0)(), 'bar');
|
||||||
equal(template._child(1)(), 'bar');
|
equal(template._child(0)(), 'bar');
|
||||||
});
|
});
|
||||||
it('should render depthed content', function() {
|
it('should render depthed content', function() {
|
||||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||||
// Calling twice to hit the non-compiled case.
|
// Calling twice to hit the non-compiled case.
|
||||||
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
|
equal(template._child(0, undefined, [], [{ bar: 'baz' }])(), 'baz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+268
-44
@@ -20,36 +20,19 @@ describe('security issues', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
|
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{constructor.name}}')
|
||||||
'{{constructor.name}}',
|
.withInput({ constructor: { name: 'here we go' } })
|
||||||
{
|
.toCompileTo('here we go');
|
||||||
constructor: {
|
|
||||||
name: 'here we go'
|
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
|
||||||
}
|
.withInput({ constructor: { name: 'here we go' } })
|
||||||
},
|
.toCompileTo('here we go');
|
||||||
'here we go'
|
|
||||||
);
|
|
||||||
shouldCompileTo(
|
|
||||||
'{{lookup (lookup this "constructor") "name"}}',
|
|
||||||
{
|
|
||||||
constructor: {
|
|
||||||
name: 'here we go'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'here we go'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow the "constructor" property to be accessed if it is an "own property"', function() {
|
it('should allow the "constructor" property to be accessed if it is an "own property"', function() {
|
||||||
shouldCompileTo(
|
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
|
||||||
'{{lookup (lookup this "constructor") "name"}}',
|
.withInput({ constructor: { name: 'here we go' } })
|
||||||
{
|
.toCompileTo('here we go');
|
||||||
constructor: {
|
|
||||||
name: 'here we go'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'here we go'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,19 +43,13 @@ describe('security issues', function() {
|
|||||||
|
|
||||||
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
|
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
|
||||||
it('should throw an exception when calling "{{helperMissing}}" ', function() {
|
it('should throw an exception when calling "{{helperMissing}}" ', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{helperMissing}}').toThrow(Error);
|
||||||
var template = Handlebars.compile('{{helperMissing}}');
|
|
||||||
template({});
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{#helperMissing}}{{/helperMissing}}').toThrow(Error);
|
||||||
var template = Handlebars.compile(
|
|
||||||
'{{#helperMissing}}{{/helperMissing}}'
|
|
||||||
);
|
|
||||||
template({});
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
||||||
var functionCalls = [];
|
var functionCalls = [];
|
||||||
expect(function() {
|
expect(function() {
|
||||||
@@ -85,17 +62,15 @@ describe('security issues', function() {
|
|||||||
}).to.throw(Error);
|
}).to.throw(Error);
|
||||||
expect(functionCalls.length).to.equal(0);
|
expect(functionCalls.length).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{#blockHelperMissing .}}{{/blockHelperMissing}}')
|
||||||
var template = Handlebars.compile(
|
.withInput({
|
||||||
'{{#blockHelperMissing .}}{{/blockHelperMissing}}'
|
|
||||||
);
|
|
||||||
template({
|
|
||||||
fn: function() {
|
fn: function() {
|
||||||
return 'functionInData';
|
return 'functionInData';
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}, Error);
|
.toThrow(Error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,12 +79,14 @@ describe('security issues', function() {
|
|||||||
var template = Handlebars.compile('{{helperMissing}}');
|
var template = Handlebars.compile('{{helperMissing}}');
|
||||||
template({}, { allowCallsToHelperMissing: true });
|
template({}, { allowCallsToHelperMissing: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
|
||||||
var template = Handlebars.compile(
|
var template = Handlebars.compile(
|
||||||
'{{#helperMissing}}{{/helperMissing}}'
|
'{{#helperMissing}}{{/helperMissing}}'
|
||||||
);
|
);
|
||||||
template({}, { allowCallsToHelperMissing: true });
|
template({}, { allowCallsToHelperMissing: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
|
||||||
var functionCalls = [];
|
var functionCalls = [];
|
||||||
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
|
||||||
@@ -123,6 +100,7 @@ describe('security issues', function() {
|
|||||||
);
|
);
|
||||||
equals(functionCalls.length, 1);
|
equals(functionCalls.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
|
||||||
var template = Handlebars.compile(
|
var template = Handlebars.compile(
|
||||||
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
|
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
|
||||||
@@ -155,11 +133,13 @@ describe('security issues', function() {
|
|||||||
'{{__defineGetter__}}',
|
'{{__defineGetter__}}',
|
||||||
'{{__defineSetter__}}',
|
'{{__defineSetter__}}',
|
||||||
'{{__lookupGetter__}}',
|
'{{__lookupGetter__}}',
|
||||||
|
'{{__lookupSetter__}}',
|
||||||
'{{__proto__}}',
|
'{{__proto__}}',
|
||||||
'{{lookup this "constructor"}}',
|
'{{lookup this "constructor"}}',
|
||||||
'{{lookup this "__defineGetter__"}}',
|
'{{lookup this "__defineGetter__"}}',
|
||||||
'{{lookup this "__defineSetter__"}}',
|
'{{lookup this "__defineSetter__"}}',
|
||||||
'{{lookup this "__lookupGetter__"}}',
|
'{{lookup this "__lookupGetter__"}}',
|
||||||
|
'{{lookup this "__lookupSetter__"}}',
|
||||||
'{{lookup this "__proto__"}}'
|
'{{lookup this "__proto__"}}'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -320,6 +300,10 @@ describe('security issues', function() {
|
|||||||
checkProtoPropertyAccess({ compat: true });
|
checkProtoPropertyAccess({ compat: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('in strict-mode', function() {
|
||||||
|
checkProtoPropertyAccess({ strict: true });
|
||||||
|
});
|
||||||
|
|
||||||
function checkProtoPropertyAccess(compileOptions) {
|
function checkProtoPropertyAccess(compileOptions) {
|
||||||
it('should be prohibited by default and log a warning', function() {
|
it('should be prohibited by default and log a warning', function() {
|
||||||
var spy = sinon.spy(console, 'error');
|
var spy = sinon.spy(console, 'error');
|
||||||
@@ -418,6 +402,246 @@ 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-2qvq-rjwj-gvw9: partial resolution must not use polluted prototypes', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete Object.prototype.widget;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve partial names from Object.prototype', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype.widget = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('<div>{{> widget}}</div>')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-2w6w-674q-4c4q, GHSA-xhpv-hc6g-r9c6, GHSA-3mfm-83xf-c92r: untrusted AST inputs', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInjectedProgram() {
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 20 }
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: {
|
||||||
|
open: false,
|
||||||
|
close: false
|
||||||
|
},
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: ['lookup'],
|
||||||
|
original: 'lookup',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: [],
|
||||||
|
original: 'this',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'NumberLiteral',
|
||||||
|
value: '{},{})) + (Function) + (({}',
|
||||||
|
original: 1,
|
||||||
|
loc: loc
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should neutralize AST NumberLiteral type confusion in compile()', function() {
|
||||||
|
// The compiler coerces NumberLiteral.value via Number() before
|
||||||
|
// emitting a pushLiteral opcode, so a type-confused string value
|
||||||
|
// becomes NaN, preventing code injection.
|
||||||
|
var template = Handlebars.compile(createInjectedProgram());
|
||||||
|
var result = template({});
|
||||||
|
expect(result).to.not.contain('Function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject AST objects passed via dynamic partial lookup', function() {
|
||||||
|
expect(function() {
|
||||||
|
var template = Handlebars.compile('{{> (lookup . "payload")}}');
|
||||||
|
template({
|
||||||
|
payload: createInjectedProgram()
|
||||||
|
});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize param depth in stringParams mode', function() {
|
||||||
|
// pushParam passes val.depth directly to the getContext opcode.
|
||||||
|
// In stringParams mode, getContext stores the depth in lastContext,
|
||||||
|
// which contextName interpolates into generated code as
|
||||||
|
// 'depths[' + depth + ']'. A malicious depth string can escape the
|
||||||
|
// bracket expression and inject arbitrary code at template runtime.
|
||||||
|
//
|
||||||
|
// With sanitization the depth becomes 0, producing 'depth0' (safe).
|
||||||
|
// Without sanitization the injected expression executes and throws.
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 20 }
|
||||||
|
};
|
||||||
|
var maliciousAST = {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: ['lookup'],
|
||||||
|
original: 'lookup',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 'function(){throw new Error("INJECTION")}()',
|
||||||
|
parts: [],
|
||||||
|
original: '',
|
||||||
|
loc: loc
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var template = Handlebars.compile(maliciousAST, {
|
||||||
|
stringParams: true
|
||||||
|
});
|
||||||
|
// After sanitization the depth is 0, so the template runs without
|
||||||
|
// executing the injected throw expression.
|
||||||
|
expect(function() {
|
||||||
|
template({});
|
||||||
|
}).to.not.throw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-442j-39wm-28r2: lookup must return checked value', function() {
|
||||||
|
it('should use the validated value from lookupProperty() in compat mode', function() {
|
||||||
|
var input = { child: {} };
|
||||||
|
var readCount = 0;
|
||||||
|
Object.defineProperty(input, 'unstable', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function() {
|
||||||
|
readCount++;
|
||||||
|
return readCount === 1 ? 'first-read' : 'second-read';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectTemplate('{{#with child}}{{unstable}}{{/with}}')
|
||||||
|
.withInput(input)
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('first-read');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-9cx6-37pm-9jff: malformed decorators should fail safely', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should throw a controlled error for unknown decorators', function() {
|
||||||
|
var template = Handlebars.compile('{{*notRegistered}}');
|
||||||
|
expect(function() {
|
||||||
|
template({});
|
||||||
|
}).to.throw(/Missing decorator|not registered/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-new: @partial-block must not resolve from polluted prototype', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete Object.prototype['partial-block'];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve @partial-block from Object.prototype', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype['partial-block'] = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('{{> @partial-block}}')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve @partial-block from Object.prototype inside a partial', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype['partial-block'] = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
Handlebars.registerPartial('testPartial', '{{> @partial-block}}');
|
||||||
|
try {
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('{{> testPartial}}')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
} finally {
|
||||||
|
Handlebars.unregisterPartial('testPartial');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still render legitimate @partial-block content', function() {
|
||||||
|
Handlebars.registerPartial('wrapper', '<div>{{> @partial-block}}</div>');
|
||||||
|
try {
|
||||||
|
var result = Handlebars.compile('{{#> wrapper}}hello{{/wrapper}}')({});
|
||||||
|
expect(result).to.equal('<div>hello</div>');
|
||||||
|
} finally {
|
||||||
|
Handlebars.unregisterPartial('wrapper');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function wrapToAdjustContainer(precompiledTemplateFunction) {
|
function wrapToAdjustContainer(precompiledTemplateFunction) {
|
||||||
|
|||||||
+6
-16
@@ -40,22 +40,12 @@ describe('spec', function() {
|
|||||||
/* eslint-enable no-eval */
|
/* eslint-enable no-eval */
|
||||||
}
|
}
|
||||||
it(name + ' - ' + test.name, function() {
|
it(name + ' - ' + test.name, function() {
|
||||||
if (test.partials) {
|
expectTemplate(test.template)
|
||||||
shouldCompileToWithPartials(
|
.withInput(data)
|
||||||
test.template,
|
.withPartials(test.partials || {})
|
||||||
[data, {}, test.partials, true],
|
.withCompileOptions({ compat: true })
|
||||||
true,
|
.withMessage(test.desc + ' "' + test.template + '"')
|
||||||
test.expected,
|
.toCompileTo(test.expected);
|
||||||
test.desc + ' "' + test.template + '"'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
shouldCompileTo(
|
|
||||||
test.template,
|
|
||||||
[data, {}, {}, true],
|
|
||||||
test.expected,
|
|
||||||
test.desc + ' "' + test.template + '"'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+95
-161
@@ -3,161 +3,107 @@ var Exception = Handlebars.Exception;
|
|||||||
describe('strict', function() {
|
describe('strict', function() {
|
||||||
describe('strict mode', function() {
|
describe('strict mode', function() {
|
||||||
it('should error on missing property lookup', function() {
|
it('should error on missing property lookup', function() {
|
||||||
shouldThrow(
|
expectTemplate('{{hello}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile('{{hello}}', { strict: true });
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
|
|
||||||
template({});
|
|
||||||
},
|
|
||||||
Exception,
|
|
||||||
/"hello" not defined in/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error on missing child', function() {
|
it('should error on missing child', function() {
|
||||||
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
|
expectTemplate('{{hello.bar}}')
|
||||||
equals(template({ hello: { bar: 'foo' } }), 'foo');
|
.withCompileOptions({ strict: true })
|
||||||
|
.withInput({ hello: { bar: 'foo' } })
|
||||||
|
.toCompileTo('foo');
|
||||||
|
|
||||||
shouldThrow(
|
expectTemplate('{{hello.bar}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
template({ hello: {} });
|
.withInput({ hello: {} })
|
||||||
},
|
.toThrow(Exception, /"bar" not defined in/);
|
||||||
Exception,
|
|
||||||
/"bar" not defined in/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle explicit undefined', function() {
|
it('should handle explicit undefined', function() {
|
||||||
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
|
expectTemplate('{{hello.bar}}')
|
||||||
|
.withCompileOptions({ strict: true })
|
||||||
equals(template({ hello: { bar: undefined } }), '');
|
.withInput({ hello: { bar: undefined } })
|
||||||
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error on missing property lookup in known helpers mode', function() {
|
it('should error on missing property lookup in known helpers mode', function() {
|
||||||
shouldThrow(
|
expectTemplate('{{hello}}')
|
||||||
function() {
|
.withCompileOptions({
|
||||||
var template = CompilerContext.compile('{{hello}}', {
|
strict: true,
|
||||||
strict: true,
|
knownHelpersOnly: true
|
||||||
knownHelpersOnly: true
|
})
|
||||||
});
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
|
|
||||||
template({});
|
|
||||||
},
|
|
||||||
Exception,
|
|
||||||
/"hello" not defined in/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('should error on missing context', function() {
|
|
||||||
shouldThrow(function() {
|
|
||||||
var template = CompilerContext.compile('{{hello}}', { strict: true });
|
|
||||||
|
|
||||||
template();
|
it('should error on missing context', function() {
|
||||||
}, Error);
|
expectTemplate('{{hello}}')
|
||||||
|
.withCompileOptions({ strict: true })
|
||||||
|
.toThrow(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error on missing data lookup', function() {
|
it('should error on missing data lookup', function() {
|
||||||
var template = CompilerContext.compile('{{@hello}}', { strict: true });
|
var xt = expectTemplate('{{@hello}}').withCompileOptions({
|
||||||
equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
|
strict: true
|
||||||
|
});
|
||||||
|
|
||||||
shouldThrow(function() {
|
xt.toThrow(Error);
|
||||||
template();
|
|
||||||
}, Error);
|
xt.withRuntimeOptions({ data: { hello: 'foo' } }).toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not run helperMissing for helper calls', function() {
|
it('should not run helperMissing for helper calls', function() {
|
||||||
shouldThrow(
|
expectTemplate('{{hello foo}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile('{{hello foo}}', {
|
.withInput({ foo: true })
|
||||||
strict: true
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
});
|
|
||||||
|
|
||||||
template({ foo: true });
|
expectTemplate('{{#hello foo}}{{/hello}}')
|
||||||
},
|
.withCompileOptions({ strict: true })
|
||||||
Exception,
|
.withInput({ foo: true })
|
||||||
/"hello" not defined in/
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
);
|
|
||||||
|
|
||||||
shouldThrow(
|
|
||||||
function() {
|
|
||||||
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
|
|
||||||
strict: true
|
|
||||||
});
|
|
||||||
|
|
||||||
template({ foo: true });
|
|
||||||
},
|
|
||||||
Exception,
|
|
||||||
/"hello" not defined in/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on ambiguous blocks', function() {
|
it('should throw on ambiguous blocks', function() {
|
||||||
shouldThrow(
|
expectTemplate('{{#hello}}{{/hello}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
strict: true
|
|
||||||
});
|
|
||||||
|
|
||||||
template({});
|
expectTemplate('{{^hello}}{{/hello}}')
|
||||||
},
|
.withCompileOptions({ strict: true })
|
||||||
Exception,
|
.toThrow(Exception, /"hello" not defined in/);
|
||||||
/"hello" not defined in/
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldThrow(
|
expectTemplate('{{#hello.bar}}{{/hello.bar}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
|
.withInput({ hello: {} })
|
||||||
strict: true
|
.toThrow(Exception, /"bar" not defined in/);
|
||||||
});
|
|
||||||
|
|
||||||
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/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow undefined parameters when passed to helpers', function() {
|
it('should allow undefined parameters when passed to helpers', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#unless foo}}success{{/unless}}')
|
||||||
'{{#unless foo}}success{{/unless}}',
|
.withCompileOptions({ strict: true })
|
||||||
{ strict: true }
|
.toCompileTo('success');
|
||||||
);
|
|
||||||
equals(template({}), 'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow undefined hash when passed to helpers', function() {
|
it('should allow undefined hash when passed to helpers', function() {
|
||||||
var template = CompilerContext.compile('{{helper value=@foo}}', {
|
expectTemplate('{{helper value=@foo}}')
|
||||||
strict: true
|
.withCompileOptions({
|
||||||
});
|
strict: true
|
||||||
var helpers = {
|
})
|
||||||
helper: function(options) {
|
.withHelpers({
|
||||||
equals('value' in options.hash, true);
|
helper: function(options) {
|
||||||
equals(options.hash.value, undefined);
|
equals('value' in options.hash, true);
|
||||||
return 'success';
|
equals(options.hash.value, undefined);
|
||||||
}
|
return 'success';
|
||||||
};
|
}
|
||||||
equals(template({}, { helpers: helpers }), 'success');
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error location on missing property lookup', function() {
|
it('should show error location on missing property lookup', function() {
|
||||||
shouldThrow(
|
expectTemplate('\n\n\n {{hello}}')
|
||||||
function() {
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile('\n\n\n {{hello}}', {
|
.toThrow(Exception, '"hello" not defined in [object Object] - 4:5');
|
||||||
strict: true
|
|
||||||
});
|
|
||||||
template({});
|
|
||||||
},
|
|
||||||
Exception,
|
|
||||||
'"hello" not defined in [object Object] - 4:5'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error contains correct location properties on missing property lookup', function() {
|
it('should error contains correct location properties on missing property lookup', function() {
|
||||||
@@ -177,54 +123,42 @@ describe('strict', function() {
|
|||||||
|
|
||||||
describe('assume objects', function() {
|
describe('assume objects', function() {
|
||||||
it('should ignore missing property', function() {
|
it('should ignore missing property', function() {
|
||||||
var template = CompilerContext.compile('{{hello}}', {
|
expectTemplate('{{hello}}')
|
||||||
assumeObjects: true
|
.withCompileOptions({ assumeObjects: true })
|
||||||
});
|
.toCompileTo('');
|
||||||
|
|
||||||
equal(template({}), '');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore missing child', function() {
|
it('should ignore missing child', function() {
|
||||||
var template = CompilerContext.compile('{{hello.bar}}', {
|
expectTemplate('{{hello.bar}}')
|
||||||
assumeObjects: true
|
.withCompileOptions({ assumeObjects: true })
|
||||||
});
|
.withInput({ hello: {} })
|
||||||
|
.toCompileTo('');
|
||||||
equal(template({ hello: {} }), '');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error on missing object', function() {
|
it('should error on missing object', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{hello.bar}}')
|
||||||
var template = CompilerContext.compile('{{hello.bar}}', {
|
.withCompileOptions({ assumeObjects: true })
|
||||||
assumeObjects: true
|
.toThrow(Error);
|
||||||
});
|
|
||||||
|
|
||||||
template({});
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
it('should error on missing context', function() {
|
|
||||||
shouldThrow(function() {
|
|
||||||
var template = CompilerContext.compile('{{hello}}', {
|
|
||||||
assumeObjects: true
|
|
||||||
});
|
|
||||||
|
|
||||||
template();
|
it('should error on missing context', function() {
|
||||||
}, Error);
|
expectTemplate('{{hello}}')
|
||||||
|
.withCompileOptions({ assumeObjects: true })
|
||||||
|
.withInput(undefined)
|
||||||
|
.toThrow(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error on missing data lookup', function() {
|
it('should error on missing data lookup', function() {
|
||||||
shouldThrow(function() {
|
expectTemplate('{{@hello.bar}}')
|
||||||
var template = CompilerContext.compile('{{@hello.bar}}', {
|
.withCompileOptions({ assumeObjects: true })
|
||||||
assumeObjects: true
|
.withInput(undefined)
|
||||||
});
|
.toThrow(Error);
|
||||||
|
|
||||||
template();
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should execute blockHelperMissing', function() {
|
it('should execute blockHelperMissing', function() {
|
||||||
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
|
expectTemplate('{{^hello}}foo{{/hello}}')
|
||||||
assumeObjects: true
|
.withCompileOptions({ assumeObjects: true })
|
||||||
});
|
.toCompileTo('foo');
|
||||||
|
|
||||||
equals(template({}), 'foo');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+152
-191
@@ -1,151 +1,117 @@
|
|||||||
describe('string params mode', function() {
|
describe('string params mode', function() {
|
||||||
it('arguments to helpers can be retrieved from options hash in string form', function() {
|
it('arguments to helpers can be retrieved from options hash in string form', function() {
|
||||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
|
expectTemplate('{{wycats is.a slave.driver}}')
|
||||||
stringParams: true
|
.withCompileOptions({
|
||||||
});
|
stringParams: true
|
||||||
|
})
|
||||||
var helpers = {
|
.withHelpers({
|
||||||
wycats: function(passiveVoice, noun) {
|
wycats: function(passiveVoice, noun) {
|
||||||
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
|
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.withMessage('String parameters output')
|
||||||
var result = template({}, { helpers: helpers });
|
.toCompileTo('HELP ME MY BOSS is.a slave.driver');
|
||||||
|
|
||||||
equals(
|
|
||||||
result,
|
|
||||||
'HELP ME MY BOSS is.a slave.driver',
|
|
||||||
'String parameters output'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
|
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#wycats is.a slave.driver}}help :({{/wycats}}')
|
||||||
'{{#wycats is.a slave.driver}}help :({{/wycats}}',
|
.withCompileOptions({
|
||||||
{ stringParams: true }
|
stringParams: true
|
||||||
);
|
})
|
||||||
|
.withHelpers({
|
||||||
var helpers = {
|
wycats: function(passiveVoice, noun, options) {
|
||||||
wycats: function(passiveVoice, noun, options) {
|
return (
|
||||||
return (
|
'HELP ME MY BOSS ' +
|
||||||
'HELP ME MY BOSS ' +
|
passiveVoice +
|
||||||
passiveVoice +
|
' ' +
|
||||||
' ' +
|
noun +
|
||||||
noun +
|
': ' +
|
||||||
': ' +
|
options.fn(this)
|
||||||
options.fn(this)
|
);
|
||||||
);
|
}
|
||||||
}
|
})
|
||||||
};
|
.withMessage('String parameters output')
|
||||||
|
.toCompileTo('HELP ME MY BOSS is.a slave.driver: help :(');
|
||||||
var result = template({}, { helpers: helpers });
|
|
||||||
|
|
||||||
equals(
|
|
||||||
result,
|
|
||||||
'HELP ME MY BOSS is.a slave.driver: help :(',
|
|
||||||
'String parameters output'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
|
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}')
|
||||||
'{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
|
.withCompileOptions({
|
||||||
{ stringParams: true }
|
stringParams: true
|
||||||
);
|
})
|
||||||
|
.withHelpers({
|
||||||
var helpers = {
|
tomdale: function(desire, noun, options) {
|
||||||
tomdale: function(desire, noun, options) {
|
return (
|
||||||
return (
|
'STOP ME FROM READING HACKER NEWS I ' +
|
||||||
'STOP ME FROM READING HACKER NEWS I ' +
|
options.contexts[0][desire] +
|
||||||
options.contexts[0][desire] +
|
' ' +
|
||||||
' ' +
|
noun
|
||||||
noun
|
);
|
||||||
);
|
},
|
||||||
},
|
with: function(context, options) {
|
||||||
|
return options.fn(options.contexts[0][context]);
|
||||||
with: function(context, options) {
|
}
|
||||||
return options.fn(options.contexts[0][context]);
|
})
|
||||||
}
|
.withInput({
|
||||||
};
|
|
||||||
|
|
||||||
var result = template(
|
|
||||||
{
|
|
||||||
dale: {},
|
dale: {},
|
||||||
|
|
||||||
need: 'need-a'
|
need: 'need-a'
|
||||||
},
|
})
|
||||||
{ helpers: helpers }
|
.withMessage('Proper context variable output')
|
||||||
);
|
.toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke');
|
||||||
|
|
||||||
equals(
|
|
||||||
result,
|
|
||||||
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
|
|
||||||
'Proper context variable output'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('information about the types is passed along', function() {
|
it('information about the types is passed along', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate("{{tomdale 'need' dad.joke true false}}")
|
||||||
"{{tomdale 'need' dad.joke true false}}",
|
.withCompileOptions({
|
||||||
{ stringParams: true }
|
stringParams: true
|
||||||
);
|
})
|
||||||
|
.withHelpers({
|
||||||
var helpers = {
|
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
||||||
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
equal(options.types[0], 'StringLiteral', 'the string type is passed');
|
||||||
equal(options.types[0], 'StringLiteral', 'the string type is passed');
|
equal(
|
||||||
equal(
|
options.types[1],
|
||||||
options.types[1],
|
'PathExpression',
|
||||||
'PathExpression',
|
'the expression type is passed'
|
||||||
'the expression type is passed'
|
);
|
||||||
);
|
equal(
|
||||||
equal(
|
options.types[2],
|
||||||
options.types[2],
|
'BooleanLiteral',
|
||||||
'BooleanLiteral',
|
'the expression type is passed'
|
||||||
'the expression type is passed'
|
);
|
||||||
);
|
equal(desire, 'need', 'the string form is passed for strings');
|
||||||
equal(desire, 'need', 'the string form is passed for strings');
|
equal(noun, 'dad.joke', 'the string form is passed for expressions');
|
||||||
equal(noun, 'dad.joke', 'the string form is passed for expressions');
|
equal(trueBool, true, 'raw booleans are passed through');
|
||||||
equal(trueBool, true, 'raw booleans are passed through');
|
equal(falseBool, false, 'raw booleans are passed through');
|
||||||
equal(falseBool, false, 'raw booleans are passed through');
|
return 'Helper called';
|
||||||
return 'Helper called';
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('Helper called');
|
||||||
|
|
||||||
var result = template({}, { helpers: helpers });
|
|
||||||
equal(result, 'Helper called');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hash parameters get type information', function() {
|
it('hash parameters get type information', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate("{{tomdale he.says desire='need' noun=dad.joke bool=true}}")
|
||||||
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
|
.withCompileOptions({
|
||||||
{ stringParams: true }
|
stringParams: true
|
||||||
);
|
})
|
||||||
|
.withHelpers({
|
||||||
|
tomdale: function(exclamation, options) {
|
||||||
|
equal(exclamation, 'he.says');
|
||||||
|
equal(options.types[0], 'PathExpression');
|
||||||
|
|
||||||
var helpers = {
|
equal(options.hashTypes.desire, 'StringLiteral');
|
||||||
tomdale: function(exclamation, options) {
|
equal(options.hashTypes.noun, 'PathExpression');
|
||||||
equal(exclamation, 'he.says');
|
equal(options.hashTypes.bool, 'BooleanLiteral');
|
||||||
equal(options.types[0], 'PathExpression');
|
equal(options.hash.desire, 'need');
|
||||||
|
equal(options.hash.noun, 'dad.joke');
|
||||||
equal(options.hashTypes.desire, 'StringLiteral');
|
equal(options.hash.bool, true);
|
||||||
equal(options.hashTypes.noun, 'PathExpression');
|
return 'Helper called';
|
||||||
equal(options.hashTypes.bool, 'BooleanLiteral');
|
}
|
||||||
equal(options.hash.desire, 'need');
|
})
|
||||||
equal(options.hash.noun, 'dad.joke');
|
.toCompileTo('Helper called');
|
||||||
equal(options.hash.bool, true);
|
|
||||||
return 'Helper called';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = template({}, { helpers: helpers });
|
|
||||||
equal(result, 'Helper called');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hash parameters get context information', function() {
|
it('hash parameters get context information', function() {
|
||||||
var template = CompilerContext.compile(
|
|
||||||
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
|
|
||||||
{ stringParams: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
var context = { dale: {} };
|
var context = { dale: {} };
|
||||||
|
|
||||||
var helpers = {
|
var helpers = {
|
||||||
@@ -165,82 +131,77 @@ describe('string params mode', function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = template(context, { helpers: helpers });
|
expectTemplate(
|
||||||
equal(result, 'Helper called');
|
"{{#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() {
|
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(
|
expectTemplate(
|
||||||
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
|
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}'
|
||||||
{ stringParams: true }
|
)
|
||||||
);
|
.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)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
var helpers = {
|
with: function(context, options) {
|
||||||
tomdale: function(desire, noun, options) {
|
return options.fn(options.contexts[0][context]);
|
||||||
return (
|
}
|
||||||
'STOP ME FROM READING HACKER NEWS I ' +
|
})
|
||||||
options.contexts[0][desire] +
|
.withInput({
|
||||||
' ' +
|
|
||||||
noun +
|
|
||||||
' ' +
|
|
||||||
options.fn(this)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
with: function(context, options) {
|
|
||||||
return options.fn(options.contexts[0][context]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = template(
|
|
||||||
{
|
|
||||||
dale: {},
|
dale: {},
|
||||||
|
|
||||||
need: 'need-a'
|
need: 'need-a'
|
||||||
},
|
})
|
||||||
{ helpers: helpers }
|
.withMessage('Proper context variable output')
|
||||||
);
|
.toCompileTo('STOP ME FROM READING HACKER NEWS I need-a dad.joke wot');
|
||||||
|
|
||||||
equals(
|
|
||||||
result,
|
|
||||||
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
|
|
||||||
'Proper context variable output'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with nested block ambiguous', function() {
|
it('with nested block ambiguous', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate(
|
||||||
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
|
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}'
|
||||||
{ stringParams: true }
|
)
|
||||||
);
|
.withCompileOptions({
|
||||||
|
stringParams: true
|
||||||
var helpers = {
|
})
|
||||||
with: function() {
|
.withHelpers({
|
||||||
return 'WITH';
|
with: function() {
|
||||||
},
|
return 'WITH';
|
||||||
view: function() {
|
},
|
||||||
return 'VIEW';
|
view: function() {
|
||||||
}
|
return 'VIEW';
|
||||||
};
|
}
|
||||||
|
})
|
||||||
var result = template({}, { helpers: helpers });
|
.toCompileTo('WITH');
|
||||||
equals(result, 'WITH');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle DATA', function() {
|
it('should handle DATA', function() {
|
||||||
var template = CompilerContext.compile('{{foo @bar}}', {
|
expectTemplate('{{foo @bar}}')
|
||||||
stringParams: true
|
.withCompileOptions({
|
||||||
});
|
stringParams: true
|
||||||
|
})
|
||||||
var helpers = {
|
.withHelpers({
|
||||||
foo: function(bar, options) {
|
foo: function(bar, options) {
|
||||||
equal(bar, '@bar');
|
equal(bar, '@bar');
|
||||||
equal(options.types[0], 'PathExpression');
|
equal(options.types[0], 'PathExpression');
|
||||||
return 'Foo!';
|
return 'Foo!';
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.toCompileTo('Foo!');
|
||||||
var result = template({}, { helpers: helpers });
|
|
||||||
equal(result, 'Foo!');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+212
-226
@@ -1,61 +1,57 @@
|
|||||||
describe('subexpressions', function() {
|
describe('subexpressions', function() {
|
||||||
it('arg-less helper', function() {
|
it('arg-less helper', function() {
|
||||||
var string = '{{foo (bar)}}!';
|
expectTemplate('{{foo (bar)}}!')
|
||||||
var context = {};
|
.withHelpers({
|
||||||
var helpers = {
|
foo: function(val) {
|
||||||
foo: function(val) {
|
return val + val;
|
||||||
return val + val;
|
},
|
||||||
},
|
bar: function() {
|
||||||
bar: function() {
|
return 'LOL';
|
||||||
return 'LOL';
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('LOLLOL!');
|
||||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('helper w args', function() {
|
it('helper w args', function() {
|
||||||
var string = '{{blog (equal a b)}}';
|
expectTemplate('{{blog (equal a b)}}')
|
||||||
|
.withInput({ bar: 'LOL' })
|
||||||
var context = { bar: 'LOL' };
|
.withHelpers({
|
||||||
var helpers = {
|
blog: function(val) {
|
||||||
blog: function(val) {
|
return 'val is ' + val;
|
||||||
return 'val is ' + val;
|
},
|
||||||
},
|
equal: function(x, y) {
|
||||||
equal: function(x, y) {
|
return x === y;
|
||||||
return x === y;
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('val is true');
|
||||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('mixed paths and helpers', function() {
|
it('mixed paths and helpers', function() {
|
||||||
var string = '{{blog baz.bat (equal a b) baz.bar}}';
|
expectTemplate('{{blog baz.bat (equal a b) baz.bar}}')
|
||||||
|
.withInput({ bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } })
|
||||||
var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
|
.withHelpers({
|
||||||
var helpers = {
|
blog: function(val, that, theOther) {
|
||||||
blog: function(val, that, theOther) {
|
return 'val is ' + val + ', ' + that + ' and ' + theOther;
|
||||||
return 'val is ' + val + ', ' + that + ' and ' + theOther;
|
},
|
||||||
},
|
equal: function(x, y) {
|
||||||
equal: function(x, y) {
|
return x === y;
|
||||||
return x === y;
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('val is foo!, true and bar!');
|
||||||
shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports much nesting', function() {
|
it('supports much nesting', function() {
|
||||||
var string = '{{blog (equal (equal true true) true)}}';
|
expectTemplate('{{blog (equal (equal true true) true)}}')
|
||||||
|
.withInput({ bar: 'LOL' })
|
||||||
var context = { bar: 'LOL' };
|
.withHelpers({
|
||||||
var helpers = {
|
blog: function(val) {
|
||||||
blog: function(val) {
|
return 'val is ' + val;
|
||||||
return 'val is ' + val;
|
},
|
||||||
},
|
equal: function(x, y) {
|
||||||
equal: function(x, y) {
|
return x === y;
|
||||||
return x === y;
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('val is true');
|
||||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('GH-800 : Complex subexpressions', function() {
|
it('GH-800 : Complex subexpressions', function() {
|
||||||
@@ -69,20 +65,33 @@ describe('subexpressions', function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate("{{dash 'abc' (concat a b)}}")
|
||||||
"{{dash 'abc' (concat a b)}}",
|
.withInput(context)
|
||||||
[context, helpers],
|
.withHelpers(helpers)
|
||||||
'abc-ab'
|
.toCompileTo('abc-ab');
|
||||||
);
|
|
||||||
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
|
expectTemplate('{{dash d (concat a b)}}')
|
||||||
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
|
.withInput(context)
|
||||||
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
|
.withHelpers(helpers)
|
||||||
shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
|
.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() {
|
it('provides each nested helper invocation its own options hash', function() {
|
||||||
var string = '{{equal (equal true true) true}}';
|
|
||||||
|
|
||||||
var lastOptions = null;
|
var lastOptions = null;
|
||||||
var helpers = {
|
var helpers = {
|
||||||
equal: function(x, y, options) {
|
equal: function(x, y, options) {
|
||||||
@@ -93,200 +102,177 @@ describe('subexpressions', function() {
|
|||||||
return x === y;
|
return x === y;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
shouldCompileTo(string, [{}, helpers], 'true');
|
expectTemplate('{{equal (equal true true) true}}')
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.toCompileTo('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with hashes', function() {
|
it('with hashes', function() {
|
||||||
var string = "{{blog (equal (equal true true) true fun='yes')}}";
|
expectTemplate("{{blog (equal (equal true true) true fun='yes')}}")
|
||||||
|
.withInput({ bar: 'LOL' })
|
||||||
var context = { bar: 'LOL' };
|
.withHelpers({
|
||||||
var helpers = {
|
blog: function(val) {
|
||||||
blog: function(val) {
|
return 'val is ' + val;
|
||||||
return 'val is ' + val;
|
},
|
||||||
},
|
equal: function(x, y) {
|
||||||
equal: function(x, y) {
|
return x === y;
|
||||||
return x === y;
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('val is true');
|
||||||
shouldCompileTo(string, [context, helpers], 'val is true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('as hashes', function() {
|
it('as hashes', function() {
|
||||||
var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}";
|
expectTemplate("{{blog fun=(equal (blog fun=1) 'val is 1')}}")
|
||||||
|
.withHelpers({
|
||||||
var helpers = {
|
blog: function(options) {
|
||||||
blog: function(options) {
|
return 'val is ' + options.hash.fun;
|
||||||
return 'val is ' + options.hash.fun;
|
},
|
||||||
},
|
equal: function(x, y) {
|
||||||
equal: function(x, y) {
|
return x === y;
|
||||||
return x === y;
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('val is true');
|
||||||
shouldCompileTo(string, [{}, helpers], 'val is true');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiple subexpressions in a hash', function() {
|
it('multiple subexpressions in a hash', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
|
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}'
|
||||||
|
)
|
||||||
var helpers = {
|
.withHelpers({
|
||||||
input: function(options) {
|
input: function(options) {
|
||||||
var hash = options.hash;
|
var hash = options.hash;
|
||||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||||
return new Handlebars.SafeString(
|
return new Handlebars.SafeString(
|
||||||
'<input aria-label="' +
|
'<input aria-label="' +
|
||||||
ariaLabel +
|
ariaLabel +
|
||||||
'" placeholder="' +
|
'" placeholder="' +
|
||||||
placeholder +
|
placeholder +
|
||||||
'" />'
|
'" />'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
t: function(defaultString) {
|
t: function(defaultString) {
|
||||||
return new Handlebars.SafeString(defaultString);
|
return new Handlebars.SafeString(defaultString);
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
shouldCompileTo(
|
.toCompileTo('<input aria-label="Name" placeholder="Example User" />');
|
||||||
string,
|
|
||||||
[{}, helpers],
|
|
||||||
'<input aria-label="Name" placeholder="Example User" />'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiple subexpressions in a hash with context', function() {
|
it('multiple subexpressions in a hash with context', function() {
|
||||||
var string =
|
expectTemplate(
|
||||||
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
|
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}'
|
||||||
|
)
|
||||||
var context = {
|
.withInput({
|
||||||
item: {
|
item: {
|
||||||
field: 'Name',
|
field: 'Name',
|
||||||
placeholder: 'Example User'
|
placeholder: 'Example User'
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.withHelpers({
|
||||||
var helpers = {
|
input: function(options) {
|
||||||
input: function(options) {
|
var hash = options.hash;
|
||||||
var hash = options.hash;
|
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
||||||
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
|
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
||||||
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
|
return new Handlebars.SafeString(
|
||||||
return new Handlebars.SafeString(
|
'<input aria-label="' +
|
||||||
'<input aria-label="' +
|
ariaLabel +
|
||||||
ariaLabel +
|
'" placeholder="' +
|
||||||
'" placeholder="' +
|
placeholder +
|
||||||
placeholder +
|
'" />'
|
||||||
'" />'
|
);
|
||||||
);
|
},
|
||||||
},
|
t: function(defaultString) {
|
||||||
t: function(defaultString) {
|
return new Handlebars.SafeString(defaultString);
|
||||||
return new Handlebars.SafeString(defaultString);
|
}
|
||||||
}
|
})
|
||||||
};
|
.toCompileTo('<input aria-label="Name" placeholder="Example User" />');
|
||||||
shouldCompileTo(
|
|
||||||
string,
|
|
||||||
[context, helpers],
|
|
||||||
'<input aria-label="Name" placeholder="Example User" />'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('in string params mode,', function() {
|
it('in string params mode,', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{snog (blorg foo x=y) yeah a=b}}')
|
||||||
'{{snog (blorg foo x=y) yeah a=b}}',
|
.withCompileOptions({ stringParams: true })
|
||||||
{ 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 helpers = {
|
blorg: function(a, options) {
|
||||||
snog: function(a, b, options) {
|
equals(
|
||||||
equals(a, 'foo');
|
options.types.length,
|
||||||
equals(
|
1,
|
||||||
options.types.length,
|
'string params for inner helper processed correctly'
|
||||||
2,
|
);
|
||||||
'string params for outer helper processed correctly'
|
equals(
|
||||||
);
|
options.types[0],
|
||||||
equals(
|
'PathExpression',
|
||||||
options.types[0],
|
'string params for inner helper processed correctly'
|
||||||
'SubExpression',
|
);
|
||||||
'string params for outer helper processed correctly'
|
return a;
|
||||||
);
|
}
|
||||||
equals(
|
})
|
||||||
options.types[1],
|
.withInput({
|
||||||
'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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = template(
|
|
||||||
{
|
|
||||||
foo: {},
|
foo: {},
|
||||||
yeah: {}
|
yeah: {}
|
||||||
},
|
})
|
||||||
{ helpers: helpers }
|
.toCompileTo('fooyeah');
|
||||||
);
|
|
||||||
|
|
||||||
equals(result, 'fooyeah');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('as hashes in string params mode', function() {
|
it('as hashes in string params mode', function() {
|
||||||
var template = CompilerContext.compile('{{blog fun=(bork)}}', {
|
expectTemplate('{{blog fun=(bork)}}')
|
||||||
stringParams: true
|
.withCompileOptions({ stringParams: true })
|
||||||
});
|
.withHelpers({
|
||||||
|
blog: function(options) {
|
||||||
var helpers = {
|
equals(options.hashTypes.fun, 'SubExpression');
|
||||||
blog: function(options) {
|
return 'val is ' + options.hash.fun;
|
||||||
equals(options.hashTypes.fun, 'SubExpression');
|
},
|
||||||
return 'val is ' + options.hash.fun;
|
bork: function() {
|
||||||
},
|
return 'BORK';
|
||||||
bork: function() {
|
}
|
||||||
return 'BORK';
|
})
|
||||||
}
|
.toCompileTo('val is BORK');
|
||||||
};
|
|
||||||
|
|
||||||
var result = template({}, { helpers: helpers });
|
|
||||||
equals(result, 'val is BORK');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subexpression functions on the context', function() {
|
it('subexpression functions on the context', function() {
|
||||||
var string = '{{foo (bar)}}!';
|
expectTemplate('{{foo (bar)}}!')
|
||||||
var context = {
|
.withInput({
|
||||||
bar: function() {
|
bar: function() {
|
||||||
return 'LOL';
|
return 'LOL';
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
var helpers = {
|
.withHelpers({
|
||||||
foo: function(val) {
|
foo: function(val) {
|
||||||
return val + val;
|
return val + val;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
.toCompileTo('LOLLOL!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("subexpressions can't just be property lookups", function() {
|
it("subexpressions can't just be property lookups", function() {
|
||||||
var string = '{{foo (bar)}}!';
|
expectTemplate('{{foo (bar)}}!')
|
||||||
var context = {
|
.withInput({
|
||||||
bar: 'LOL'
|
bar: 'LOL'
|
||||||
};
|
})
|
||||||
var helpers = {
|
.withHelpers({
|
||||||
foo: function(val) {
|
foo: function(val) {
|
||||||
return val + val;
|
return val + val;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
shouldThrow(function() {
|
.toThrow();
|
||||||
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+213
-279
@@ -5,216 +5,184 @@ describe('track ids', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not include anything without the flag', function() {
|
it('should not include anything without the flag', function() {
|
||||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}');
|
expectTemplate('{{wycats is.a slave.driver}}')
|
||||||
|
.withHelpers({
|
||||||
|
wycats: function(passiveVoice, noun, options) {
|
||||||
|
equal(options.ids, undefined);
|
||||||
|
equal(options.hashIds, undefined);
|
||||||
|
|
||||||
var helpers = {
|
return 'success';
|
||||||
wycats: function(passiveVoice, noun, options) {
|
}
|
||||||
equal(options.ids, undefined);
|
})
|
||||||
equal(options.hashIds, undefined);
|
.toCompileTo('success');
|
||||||
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(template({}, { helpers: helpers }), 'success');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include argument ids', function() {
|
it('should include argument ids', function() {
|
||||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
|
expectTemplate('{{wycats is.a slave.driver}}')
|
||||||
trackIds: true
|
.withCompileOptions({ trackIds: true })
|
||||||
});
|
.withHelpers({
|
||||||
|
wycats: function(passiveVoice, noun, options) {
|
||||||
|
equal(options.ids[0], 'is.a');
|
||||||
|
equal(options.ids[1], 'slave.driver');
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
wycats: function(passiveVoice, noun, options) {
|
'HELP ME MY BOSS ' +
|
||||||
equal(options.ids[0], 'is.a');
|
options.ids[0] +
|
||||||
equal(options.ids[1], 'slave.driver');
|
':' +
|
||||||
|
passiveVoice +
|
||||||
return (
|
' ' +
|
||||||
'HELP ME MY BOSS ' +
|
options.ids[1] +
|
||||||
options.ids[0] +
|
':' +
|
||||||
':' +
|
noun
|
||||||
passiveVoice +
|
);
|
||||||
' ' +
|
}
|
||||||
options.ids[1] +
|
})
|
||||||
':' +
|
.withInput(context)
|
||||||
noun
|
.toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar');
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template(context, { helpers: helpers }),
|
|
||||||
'HELP ME MY BOSS is.a:foo slave.driver:bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include hash ids', function() {
|
it('should include hash ids', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{wycats bat=is.a baz=slave.driver}}')
|
||||||
'{{wycats bat=is.a baz=slave.driver}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers({
|
||||||
);
|
wycats: function(options) {
|
||||||
|
equal(options.hashIds.bat, 'is.a');
|
||||||
|
equal(options.hashIds.baz, 'slave.driver');
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
wycats: function(options) {
|
'HELP ME MY BOSS ' +
|
||||||
equal(options.hashIds.bat, 'is.a');
|
options.hashIds.bat +
|
||||||
equal(options.hashIds.baz, 'slave.driver');
|
':' +
|
||||||
|
options.hash.bat +
|
||||||
return (
|
' ' +
|
||||||
'HELP ME MY BOSS ' +
|
options.hashIds.baz +
|
||||||
options.hashIds.bat +
|
':' +
|
||||||
':' +
|
options.hash.baz
|
||||||
options.hash.bat +
|
);
|
||||||
' ' +
|
}
|
||||||
options.hashIds.baz +
|
})
|
||||||
':' +
|
.withInput(context)
|
||||||
options.hash.baz
|
.toCompileTo('HELP ME MY BOSS is.a:foo slave.driver:bar');
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template(context, { helpers: helpers }),
|
|
||||||
'HELP ME MY BOSS is.a:foo slave.driver:bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should note ../ and ./ references', function() {
|
it('should note ../ and ./ references', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{wycats ./is.a ../slave.driver this.is.a this}}')
|
||||||
'{{wycats ./is.a ../slave.driver this.is.a this}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ 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], '');
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
wycats: function(passiveVoice, noun, thiz, thiz2, options) {
|
'HELP ME MY BOSS ' +
|
||||||
equal(options.ids[0], 'is.a');
|
options.ids[0] +
|
||||||
equal(options.ids[1], '../slave.driver');
|
':' +
|
||||||
equal(options.ids[2], 'is.a');
|
passiveVoice +
|
||||||
equal(options.ids[3], '');
|
' ' +
|
||||||
|
options.ids[1] +
|
||||||
return (
|
':' +
|
||||||
'HELP ME MY BOSS ' +
|
noun
|
||||||
options.ids[0] +
|
);
|
||||||
':' +
|
}
|
||||||
passiveVoice +
|
})
|
||||||
' ' +
|
.withInput(context)
|
||||||
options.ids[1] +
|
.toCompileTo('HELP ME MY BOSS is.a:foo ../slave.driver:undefined');
|
||||||
':' +
|
|
||||||
noun
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template(context, { helpers: helpers }),
|
|
||||||
'HELP ME MY BOSS is.a:foo ../slave.driver:undefined'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should note @data references', function() {
|
it('should note @data references', function() {
|
||||||
var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {
|
expectTemplate('{{wycats @is.a @slave.driver}}')
|
||||||
trackIds: true
|
.withCompileOptions({ trackIds: true })
|
||||||
});
|
.withHelpers({
|
||||||
|
wycats: function(passiveVoice, noun, options) {
|
||||||
|
equal(options.ids[0], '@is.a');
|
||||||
|
equal(options.ids[1], '@slave.driver');
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
wycats: function(passiveVoice, noun, options) {
|
'HELP ME MY BOSS ' +
|
||||||
equal(options.ids[0], '@is.a');
|
options.ids[0] +
|
||||||
equal(options.ids[1], '@slave.driver');
|
':' +
|
||||||
|
passiveVoice +
|
||||||
return (
|
' ' +
|
||||||
'HELP ME MY BOSS ' +
|
options.ids[1] +
|
||||||
options.ids[0] +
|
':' +
|
||||||
':' +
|
noun
|
||||||
passiveVoice +
|
);
|
||||||
' ' +
|
}
|
||||||
options.ids[1] +
|
})
|
||||||
':' +
|
.withRuntimeOptions({ data: context })
|
||||||
noun
|
.toCompileTo('HELP ME MY BOSS @is.a:foo @slave.driver:bar');
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template({}, { helpers: helpers, data: context }),
|
|
||||||
'HELP ME MY BOSS @is.a:foo @slave.driver:bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null for constants', function() {
|
it('should return null for constants', function() {
|
||||||
var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {
|
expectTemplate('{{wycats 1 "foo" key=false}}')
|
||||||
trackIds: true
|
.withCompileOptions({ trackIds: true })
|
||||||
});
|
.withHelpers({
|
||||||
|
wycats: function(passiveVoice, noun, options) {
|
||||||
|
equal(options.ids[0], null);
|
||||||
|
equal(options.ids[1], null);
|
||||||
|
equal(options.hashIds.key, null);
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
wycats: function(passiveVoice, noun, options) {
|
'HELP ME MY BOSS ' +
|
||||||
equal(options.ids[0], null);
|
passiveVoice +
|
||||||
equal(options.ids[1], null);
|
' ' +
|
||||||
equal(options.hashIds.key, null);
|
noun +
|
||||||
|
' ' +
|
||||||
return (
|
options.hash.key
|
||||||
'HELP ME MY BOSS ' +
|
);
|
||||||
passiveVoice +
|
}
|
||||||
' ' +
|
})
|
||||||
noun +
|
.withInput(context)
|
||||||
' ' +
|
.toCompileTo('HELP ME MY BOSS 1 foo false');
|
||||||
options.hash.key
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template(context, { helpers: helpers }),
|
|
||||||
'HELP ME MY BOSS 1 foo false'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true for subexpressions', function() {
|
it('should return true for subexpressions', function() {
|
||||||
var template = CompilerContext.compile('{{wycats (sub)}}', {
|
expectTemplate('{{wycats (sub)}}')
|
||||||
trackIds: true
|
.withCompileOptions({ trackIds: true })
|
||||||
});
|
.withHelpers({
|
||||||
|
sub: function() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
wycats: function(passiveVoice, options) {
|
||||||
|
equal(options.ids[0], true);
|
||||||
|
|
||||||
var helpers = {
|
return 'HELP ME MY BOSS ' + passiveVoice;
|
||||||
sub: function() {
|
}
|
||||||
return 1;
|
})
|
||||||
},
|
.withInput(context)
|
||||||
wycats: function(passiveVoice, options) {
|
.toCompileTo('HELP ME MY BOSS 1');
|
||||||
equal(options.ids[0], true);
|
|
||||||
|
|
||||||
return 'HELP ME MY BOSS ' + passiveVoice;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use block param paths', function() {
|
it('should use block param paths', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}')
|
||||||
'{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ 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');
|
||||||
|
|
||||||
var helpers = {
|
return (
|
||||||
doIt: function(options) {
|
'HELP ME MY BOSS ' +
|
||||||
var blockParams = [this.is];
|
options.ids[0] +
|
||||||
blockParams.path = ['zomg'];
|
':' +
|
||||||
return options.fn(this, { blockParams: blockParams });
|
passiveVoice +
|
||||||
},
|
' ' +
|
||||||
wycats: function(passiveVoice, noun, blah, options) {
|
options.ids[1] +
|
||||||
equal(options.ids[0], 'zomg.a');
|
':' +
|
||||||
equal(options.ids[1], 'slave.driver');
|
noun
|
||||||
equal(options.ids[2], 'zomg');
|
);
|
||||||
|
}
|
||||||
return (
|
})
|
||||||
'HELP ME MY BOSS ' +
|
.withInput(context)
|
||||||
options.ids[0] +
|
.toCompileTo('HELP ME MY BOSS zomg.a:foo slave.driver:bar');
|
||||||
':' +
|
|
||||||
passiveVoice +
|
|
||||||
' ' +
|
|
||||||
options.ids[1] +
|
|
||||||
':' +
|
|
||||||
noun
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
equals(
|
|
||||||
template(context, { helpers: helpers }),
|
|
||||||
'HELP ME MY BOSS zomg.a:foo slave.driver:bar'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('builtin helpers', function() {
|
describe('builtin helpers', function() {
|
||||||
@@ -229,119 +197,85 @@ describe('track ids', function() {
|
|||||||
|
|
||||||
describe('#each', function() {
|
describe('#each', function() {
|
||||||
it('should track contextPath for arrays', function() {
|
it('should track contextPath for arrays', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#each array}}{{wycats name}}{{/each}}')
|
||||||
'{{#each array}}{{wycats name}}{{/each}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
|
||||||
|
.toCompileTo('foo:array.0\nbar:array.1\n');
|
||||||
equals(
|
|
||||||
template(
|
|
||||||
{ array: [{ name: 'foo' }, { name: 'bar' }] },
|
|
||||||
{ helpers: helpers }
|
|
||||||
),
|
|
||||||
'foo:array.0\nbar:array.1\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should track contextPath for keys', function() {
|
it('should track contextPath for keys', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#each object}}{{wycats name}}{{/each}}')
|
||||||
'{{#each object}}{{wycats name}}{{/each}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } })
|
||||||
|
.toCompileTo('foo:object.foo\nbar:object.bar\n');
|
||||||
equals(
|
|
||||||
template(
|
|
||||||
{ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } },
|
|
||||||
{ helpers: helpers }
|
|
||||||
),
|
|
||||||
'foo:object.foo\nbar:object.bar\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nesting', function() {
|
it('should handle nesting', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate(
|
||||||
'{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}',
|
'{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}'
|
||||||
{ trackIds: true }
|
)
|
||||||
);
|
.withCompileOptions({ trackIds: true })
|
||||||
|
.withHelpers(helpers)
|
||||||
equals(
|
.withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
|
||||||
template(
|
.toCompileTo('foo:.array..0\nbar:.array..1\n');
|
||||||
{ array: [{ name: 'foo' }, { name: 'bar' }] },
|
|
||||||
{ helpers: helpers }
|
|
||||||
),
|
|
||||||
'foo:.array..0\nbar:.array..1\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('should handle block params', function() {
|
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#each array as |value|}}{{blockParams value.name}}{{/each}}',
|
|
||||||
{ trackIds: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
equals(
|
it('should handle block params', function() {
|
||||||
template(
|
expectTemplate(
|
||||||
{ array: [{ name: 'foo' }, { name: 'bar' }] },
|
'{{#each array as |value|}}{{blockParams value.name}}{{/each}}'
|
||||||
{ helpers: helpers }
|
)
|
||||||
),
|
.withCompileOptions({ trackIds: true })
|
||||||
'foo:array.0.name\nbar:array.1.name\n'
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ array: [{ name: 'foo' }, { name: 'bar' }] })
|
||||||
|
.toCompileTo('foo:array.0.name\nbar:array.1.name\n');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#with', function() {
|
describe('#with', function() {
|
||||||
it('should track contextPath', function() {
|
it('should track contextPath', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#with field}}{{wycats name}}{{/with}}')
|
||||||
'{{#with field}}{{wycats name}}{{/with}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ field: { name: 'foo' } })
|
||||||
|
.toCompileTo('foo:field\n');
|
||||||
equals(
|
|
||||||
template({ field: { name: 'foo' } }, { helpers: helpers }),
|
|
||||||
'foo:field\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('should handle nesting', function() {
|
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}',
|
|
||||||
{ trackIds: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
equals(
|
it('should handle nesting', function() {
|
||||||
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
|
expectTemplate(
|
||||||
'foo:bat.field\n'
|
'{{#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() {
|
describe('#blockHelperMissing', function() {
|
||||||
it('should track contextPath for arrays', function() {
|
it('should track contextPath for arrays', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#field}}{{wycats name}}{{/field}}')
|
||||||
'{{#field}}{{wycats name}}{{/field}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ field: [{ name: 'foo' }] })
|
||||||
|
.toCompileTo('foo:field.0\n');
|
||||||
equals(
|
|
||||||
template({ field: [{ name: 'foo' }] }, { helpers: helpers }),
|
|
||||||
'foo:field.0\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should track contextPath for keys', function() {
|
it('should track contextPath for keys', function() {
|
||||||
var template = CompilerContext.compile(
|
expectTemplate('{{#field}}{{wycats name}}{{/field}}')
|
||||||
'{{#field}}{{wycats name}}{{/field}}',
|
.withCompileOptions({ trackIds: true })
|
||||||
{ trackIds: true }
|
.withHelpers(helpers)
|
||||||
);
|
.withInput({ field: { name: 'foo' } })
|
||||||
|
.toCompileTo('foo:field\n');
|
||||||
equals(
|
|
||||||
template({ field: { name: 'foo' } }, { helpers: helpers }),
|
|
||||||
'foo:field\n'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
it('should handle nesting', function() {
|
|
||||||
var template = CompilerContext.compile(
|
|
||||||
'{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}',
|
|
||||||
{ trackIds: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
equals(
|
it('should handle nesting', function() {
|
||||||
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
|
expectTemplate('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}')
|
||||||
'foo:bat.field\n'
|
.withCompileOptions({ trackIds: true })
|
||||||
);
|
.withHelpers(helpers)
|
||||||
|
.withInput({ bat: { field: { name: 'foo' } } })
|
||||||
|
.toCompileTo('foo:bat.field\n');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
|
|||||||
+3
-5
@@ -15,11 +15,9 @@ describe('utils', function() {
|
|||||||
it('it should not escape SafeString properties', function() {
|
it('it should not escape SafeString properties', function() {
|
||||||
var name = new Handlebars.SafeString('<em>Sean O'Malley</em>');
|
var name = new Handlebars.SafeString('<em>Sean O'Malley</em>');
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{name}}')
|
||||||
'{{name}}',
|
.withInput({ name: name })
|
||||||
[{ name: name }],
|
.toCompileTo('<em>Sean O'Malley</em>');
|
||||||
'<em>Sean O'Malley</em>'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+118
-87
@@ -2,125 +2,156 @@ describe('whitespace control', function() {
|
|||||||
it('should strip whitespace around mustache calls', function() {
|
it('should strip whitespace around mustache calls', function() {
|
||||||
var hash = { foo: 'bar<' };
|
var hash = { foo: 'bar<' };
|
||||||
|
|
||||||
shouldCompileTo(' {{~foo~}} ', hash, 'bar<');
|
expectTemplate(' {{~foo~}} ')
|
||||||
shouldCompileTo(' {{~foo}} ', hash, 'bar< ');
|
.withInput(hash)
|
||||||
shouldCompileTo(' {{foo~}} ', hash, ' bar<');
|
.toCompileTo('bar<');
|
||||||
|
|
||||||
shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
|
expectTemplate(' {{~foo}} ')
|
||||||
shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
|
.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() {
|
describe('blocks', function() {
|
||||||
it('should strip whitespace around simple block calls', function() {
|
it('should strip whitespace around simple block calls', function() {
|
||||||
var hash = { foo: 'bar<' };
|
var hash = { foo: 'bar<' };
|
||||||
|
|
||||||
shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
|
expectTemplate(' {{~#if foo~}} bar {{~/if~}} ')
|
||||||
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
|
.withInput(hash)
|
||||||
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
|
.toCompileTo('bar');
|
||||||
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
|
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate(' {{#if foo~}} bar {{/if~}} ')
|
||||||
' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
|
.withInput(hash)
|
||||||
hash,
|
.toCompileTo(' bar ');
|
||||||
'bar'
|
|
||||||
);
|
expectTemplate(' {{~#if foo}} bar {{~/if}} ')
|
||||||
shouldCompileTo(
|
.withInput(hash)
|
||||||
' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ',
|
.toCompileTo(' bar ');
|
||||||
hash,
|
|
||||||
' abara '
|
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() {
|
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');
|
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(
|
expectTemplate(' {{~^if foo}} bar {{~/if}} ').toCompileTo(' bar ');
|
||||||
' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
|
|
||||||
hash,
|
expectTemplate(' {{^if foo}} bar {{/if}} ').toCompileTo(' bar ');
|
||||||
'bar'
|
|
||||||
);
|
expectTemplate(
|
||||||
|
' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n '
|
||||||
|
).toCompileTo('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should strip whitespace around complex block calls', function() {
|
it('should strip whitespace around complex block calls', function() {
|
||||||
var hash = { foo: 'bar<' };
|
var hash = { foo: 'bar<' };
|
||||||
|
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
|
expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}')
|
||||||
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
|
.withInput(hash)
|
||||||
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar');
|
.toCompileTo('bar');
|
||||||
shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar ');
|
|
||||||
|
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
|
expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}')
|
||||||
|
.withInput(hash)
|
||||||
|
.toCompileTo('bar ');
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}')
|
||||||
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
|
.withInput(hash)
|
||||||
hash,
|
.toCompileTo(' bar');
|
||||||
'bar'
|
|
||||||
);
|
|
||||||
shouldCompileTo(
|
|
||||||
'\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
|
|
||||||
hash,
|
|
||||||
'bar<'
|
|
||||||
);
|
|
||||||
|
|
||||||
hash = {};
|
expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}')
|
||||||
|
.withInput(hash)
|
||||||
|
.toCompileTo(' bar ');
|
||||||
|
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
|
expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}')
|
||||||
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz ');
|
.withInput(hash)
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz');
|
.toCompileTo('bar');
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
|
|
||||||
|
|
||||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
|
expectTemplate(
|
||||||
|
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n'
|
||||||
|
)
|
||||||
|
.withInput(hash)
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
shouldCompileTo(
|
expectTemplate(
|
||||||
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
|
'\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n'
|
||||||
hash,
|
)
|
||||||
|
.withInput(hash)
|
||||||
|
.toCompileTo('bar<');
|
||||||
|
|
||||||
|
expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo(
|
||||||
'baz'
|
'baz'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expectTemplate('{{#if foo}} bar {{~^~}} baz {{/if}}').toCompileTo('baz ');
|
||||||
|
|
||||||
|
expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo(
|
||||||
|
' baz'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo(
|
||||||
|
' baz '
|
||||||
|
);
|
||||||
|
|
||||||
|
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() {
|
it('should strip whitespace around partials', function() {
|
||||||
shouldCompileToWithPartials(
|
expectTemplate('foo {{~> dude~}} ')
|
||||||
'foo {{~> dude~}} ',
|
.withPartials({ dude: 'bar' })
|
||||||
[{}, {}, { dude: 'bar' }],
|
.toCompileTo('foobar');
|
||||||
true,
|
|
||||||
'foobar'
|
|
||||||
);
|
|
||||||
shouldCompileToWithPartials(
|
|
||||||
'foo {{> dude~}} ',
|
|
||||||
[{}, {}, { dude: 'bar' }],
|
|
||||||
true,
|
|
||||||
'foo bar'
|
|
||||||
);
|
|
||||||
shouldCompileToWithPartials(
|
|
||||||
'foo {{> dude}} ',
|
|
||||||
[{}, {}, { dude: 'bar' }],
|
|
||||||
true,
|
|
||||||
'foo bar '
|
|
||||||
);
|
|
||||||
|
|
||||||
shouldCompileToWithPartials(
|
expectTemplate('foo {{> dude~}} ')
|
||||||
'foo\n {{~> dude}} ',
|
.withPartials({ dude: 'bar' })
|
||||||
[{}, {}, { dude: 'bar' }],
|
.toCompileTo('foo bar');
|
||||||
true,
|
|
||||||
'foobar'
|
expectTemplate('foo {{> dude}} ')
|
||||||
);
|
.withPartials({ dude: 'bar' })
|
||||||
shouldCompileToWithPartials(
|
.toCompileTo('foo bar ');
|
||||||
'foo\n {{> dude}} ',
|
|
||||||
[{}, {}, { dude: 'bar' }],
|
expectTemplate('foo\n {{~> dude}} ')
|
||||||
true,
|
.withPartials({ dude: 'bar' })
|
||||||
'foo\n bar'
|
.toCompileTo('foobar');
|
||||||
);
|
|
||||||
|
expectTemplate('foo\n {{> dude}} ')
|
||||||
|
.withPartials({ dude: 'bar' })
|
||||||
|
.toCompileTo('foo\n bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only strip whitespace once', function() {
|
it('should only strip whitespace once', function() {
|
||||||
var hash = { foo: 'bar' };
|
expectTemplate(' {{~foo~}} {{foo}} {{foo}} ')
|
||||||
|
.withInput({ foo: 'bar' })
|
||||||
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
|
.toCompileTo('barbar bar ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const metrics = require('../bench');
|
const metrics = require('../tests/bench');
|
||||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Use `mocha tasks/task-tests` to run these tests
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Use `mocha tasks/tests` to run these tests
|
||||||
@@ -23,7 +23,8 @@ async function execFileWithInheritedOutput(command, args) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const resolvedCommand = preferLocalDependencies(command);
|
const resolvedCommand = preferLocalDependencies(command);
|
||||||
const child = childProcess.spawn(resolvedCommand, args, {
|
const child = childProcess.spawn(resolvedCommand, args, {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
|
shell: process.platform === 'win32' // Workaround for CVE-2024-27980
|
||||||
});
|
});
|
||||||
child.on('exit', code => {
|
child.on('exit', code => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['eslint:recommended', 'prettier'],
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 6
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ var _ = require('underscore'),
|
|||||||
|
|
||||||
module.exports = function(grunt, callback) {
|
module.exports = function(grunt, callback) {
|
||||||
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
||||||
var Handlebars = require('../lib');
|
var Handlebars = require('../../lib');
|
||||||
|
|
||||||
var templateSizes = {};
|
var templateSizes = {};
|
||||||
_.each(templates, function(info, template) {
|
_.each(templates, function(info, template) {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user