Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fd0d7cd2b | |||
| 3105ca73c1 | |||
| 0e1d35582a | |||
| b10cec2322 | |||
| 800c3db6b2 | |||
| be945bb7bd | |||
| 824e3789c8 | |||
| f8c6677458 | |||
| f1421eb2ab | |||
| b5ad730473 | |||
| ff02463429 | |||
| a8a1c9dd4f | |||
| 243060b310 | |||
| 60d17f546d | |||
| 61659fbe63 | |||
| c1bc257d39 | |||
| 1b321a8bef | |||
| 92e842f82e | |||
| 6f1de2025e | |||
| d025292e06 | |||
| 71350d90ec | |||
| bf93bafeca | |||
| 70b8f1145d | |||
| 2f68d9425b | |||
| 169ef75066 | |||
| d65683434d | |||
| 1245255892 | |||
| 22e04868c0 | |||
| a3ac164642 | |||
| 864b721fef | |||
| cc8574e631 | |||
| f422bfdf3e | |||
| 25c696b889 | |||
| 1fc4ef09c1 | |||
| ce1f2abf5a | |||
| 2afecb25db | |||
| cb828aa282 | |||
| 0bc9f634ca | |||
| ae83edd5c4 |
@@ -1,23 +0,0 @@
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
/tmp/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
.idea
|
||||
yarn-error.log
|
||||
node_modules
|
||||
/handlebars-release.tgz
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
/coverage/
|
||||
/dist/
|
||||
/tests/integration/*/dist/
|
||||
|
||||
# Third-party or files that must remain unchanged
|
||||
/spec/expected/
|
||||
/spec/vendor
|
||||
|
||||
# JS-Snippets
|
||||
src/*.js
|
||||
@@ -1,64 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
|
||||
globals: {
|
||||
self: false,
|
||||
},
|
||||
env: {
|
||||
node: true,
|
||||
es2020: true,
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'warn',
|
||||
|
||||
// temporarily disabled until the violating places are fixed.
|
||||
'no-func-assign': 'off',
|
||||
'no-sparse-arrays': 'off',
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
'default-case': 'warn',
|
||||
'guard-for-in': 'warn',
|
||||
'no-alert': 'error',
|
||||
'no-caller': 'error',
|
||||
'no-div-regex': 'warn',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'error',
|
||||
'no-floating-decimal': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-iterator': 'error',
|
||||
'no-labels': 'error',
|
||||
'no-lone-blocks': 'error',
|
||||
'no-loop-func': 'error',
|
||||
'no-multi-str': 'warn',
|
||||
'no-global-assign': 'error',
|
||||
'no-new': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-process-env': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-warning-comments': 'warn',
|
||||
'no-with': 'error',
|
||||
radix: 'error',
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
'no-label-var': 'error',
|
||||
'no-undef-init': 'error',
|
||||
'no-use-before-define': ['error', 'nofunc'],
|
||||
|
||||
// ECMAScript 6 //
|
||||
//--------------//
|
||||
'no-var': 'error',
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
# Upgrade to Prettier 2.7
|
||||
3d228334530860a6e3f99dc10777c84bf22292c1
|
||||
# Format markdown files with Prettier
|
||||
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
|
||||
dfe2eaaf20f0b679d94e5a799757c4394d80f1cc
|
||||
# migrate to oxlint and oxfmt
|
||||
0c1d00282ca619c3416ad819bdf53c0852b32415
|
||||
|
||||
@@ -7,6 +7,6 @@ Generally we like to see pull requests that
|
||||
- [ ] Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- [ ] Have good commit messages
|
||||
- [ ] Have tests
|
||||
- [ ] Have the [typings](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) (lib/handlebars.d.ts) updated on every API change. If you need help, updating those, please mention that in the PR description.
|
||||
- [ ] Have the [typings](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) (types/index.d.ts) updated on every API change. If you need help, updating those, please mention that in the PR description.
|
||||
- [ ] Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
|
||||
- [ ] Currently, the `4.x`-branch contains the latest version. Please target that branch in the PR.
|
||||
- [ ] Please target the `master` branch in the PR.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
directory: '/'
|
||||
open-pull-requests-limit: 0
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
+20
-41
@@ -12,12 +12,12 @@ jobs:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '24'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
@@ -25,31 +25,6 @@ jobs:
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
dependencies:
|
||||
name: Test (dependencies)
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
# Node 14 ships with npm v6, which doesn't install peer-dependencies by default.
|
||||
# Starting with npm v7 (which is shipped with Node >= 16), peer-dependencies are
|
||||
# automatically installed. So this test (check for unmet peer-dependencies) only
|
||||
# works with Node <= 14.
|
||||
node-version: '14'
|
||||
|
||||
# Simulate an installation by a dependent package
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rm package-lock.json
|
||||
npm install --production
|
||||
|
||||
- name: Check dependency tree
|
||||
run: npm ls
|
||||
|
||||
test:
|
||||
name: Test (Node)
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
@@ -58,16 +33,16 @@ jobs:
|
||||
matrix:
|
||||
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||
# https://nodejs.org/en/about/releases/
|
||||
node-version: ['12', '14', '16', '18', '20']
|
||||
node-version: ['20', '22', '24']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -77,25 +52,27 @@ jobs:
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Test (Publish)
|
||||
if: matrix.node-version != '20'
|
||||
run: npx vitest run --project publish
|
||||
|
||||
- name: Test (Integration)
|
||||
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 -
|
||||
if: matrix.operating-system == 'ubuntu-latest'
|
||||
run: npm run test:integration
|
||||
|
||||
browser:
|
||||
name: Test (Browser)
|
||||
runs-on: 'ubuntu-22.04'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '24'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
@@ -106,7 +83,9 @@ jobs:
|
||||
npx playwright install
|
||||
|
||||
- name: Build
|
||||
run: npx grunt prepare
|
||||
run: npm run build
|
||||
|
||||
- name: Test
|
||||
run: npm run test:browser
|
||||
run: |
|
||||
npm run test:browser-smoke
|
||||
npm run test:browser
|
||||
|
||||
@@ -15,14 +15,14 @@ jobs:
|
||||
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '24'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
@@ -33,6 +33,7 @@ jobs:
|
||||
git config --global user.name "handlebars-lang"
|
||||
npm run publish:aws
|
||||
env:
|
||||
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||
S3_BUCKET_NAME: 'builds.handlebarsjs.com'
|
||||
S3_REGION: 'us-east-1'
|
||||
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -14,5 +14,6 @@ node_modules
|
||||
# Generated files
|
||||
/coverage/
|
||||
/dist/
|
||||
/tests/bench/results/
|
||||
/tests/integration/*/dist/
|
||||
/spec/tmp/*
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxfmt-config-schema/refs/heads/main/schema.json",
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80,
|
||||
"ignorePatterns": [
|
||||
".rvmrc",
|
||||
".DS_Store",
|
||||
"/tmp/",
|
||||
"*.sublime-project",
|
||||
"*.sublime-workspace",
|
||||
"npm-debug.log",
|
||||
"sauce_connect.log*",
|
||||
".idea",
|
||||
"yarn-error.log",
|
||||
"/coverage/",
|
||||
".nyc_output/",
|
||||
"/dist/",
|
||||
"/tests/integration/*/dist/",
|
||||
"/spec/expected/",
|
||||
"/spec/mustache",
|
||||
"/spec/vendor",
|
||||
"*.handlebars",
|
||||
"*.hbs"
|
||||
]
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
||||
"plugins": ["eslint", "typescript", "unicorn", "oxc", "node", "vitest"],
|
||||
"categories": {
|
||||
"correctness": "error"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "warn",
|
||||
"no-func-assign": "off",
|
||||
"no-sparse-arrays": "off",
|
||||
|
||||
"default-case": "warn",
|
||||
"guard-for-in": "warn",
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
"no-div-regex": "warn",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-str": "warn",
|
||||
"no-global-assign": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-proto": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-warning-comments": "warn",
|
||||
"no-with": "error",
|
||||
"radix": "error",
|
||||
|
||||
"no-label-var": "error",
|
||||
"no-use-before-define": ["error", { "functions": false }],
|
||||
|
||||
"no-var": "error",
|
||||
|
||||
"node/no-process-env": "error"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"tmp/",
|
||||
"dist/",
|
||||
"coverage/",
|
||||
".nyc_output/",
|
||||
"handlebars-release.tgz",
|
||||
"tests/integration/*/dist/",
|
||||
"spec/expected/",
|
||||
"spec/mustache",
|
||||
"spec/vendor",
|
||||
"node_modules",
|
||||
"types/"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["lib/**/*.js"],
|
||||
"env": {
|
||||
"node": false,
|
||||
"browser": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["spec/**/*.js"],
|
||||
"globals": {
|
||||
"CompilerContext": "readonly",
|
||||
"Handlebars": "writable",
|
||||
"handlebarsEnv": "readonly",
|
||||
"expectTemplate": "readonly",
|
||||
"suite": "readonly",
|
||||
"test": "readonly",
|
||||
"testBoth": "readonly",
|
||||
"raises": "readonly",
|
||||
"deepEqual": "readonly",
|
||||
"start": "readonly",
|
||||
"stop": "readonly",
|
||||
"ok": "readonly",
|
||||
"vi": "readonly",
|
||||
"strictEqual": "readonly",
|
||||
"define": "readonly",
|
||||
"expect": "readonly",
|
||||
"beforeEach": "readonly",
|
||||
"afterEach": "readonly",
|
||||
"describe": "readonly",
|
||||
"it": "readonly"
|
||||
},
|
||||
"rules": {
|
||||
"no-var": "off",
|
||||
"dot-notation": "off",
|
||||
"vitest/no-conditional-tests": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tasks/**/*.js"],
|
||||
"rules": {
|
||||
"node/no-process-env": "off",
|
||||
"prefer-const": "warn",
|
||||
"dot-notation": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tasks/tests/**/*.js"],
|
||||
"globals": {
|
||||
"describe": "readonly",
|
||||
"it": "readonly",
|
||||
"expect": "readonly",
|
||||
"beforeEach": "readonly",
|
||||
"afterEach": "readonly",
|
||||
"vi": "readonly"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/bench/**/*.js"],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/integration/multi-nodejs-test/**/*.js"],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-var": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/browser/**/*.js"],
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"tests/integration/webpack-babel-test/src/**/*.js",
|
||||
"tests/integration/webpack-test/src/**/*.js"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"no-var": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
/tmp/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
.idea
|
||||
yarn-error.log
|
||||
node_modules
|
||||
/handlebars-release.tgz
|
||||
.nyc_output
|
||||
|
||||
# Generated files
|
||||
/coverage/
|
||||
/dist/
|
||||
/tests/integration/*/dist/
|
||||
|
||||
# Third-party or files that must remain unchanged
|
||||
/spec/expected/
|
||||
/spec/vendor
|
||||
+12
-32
@@ -18,11 +18,8 @@ Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site sh
|
||||
|
||||
## Branches
|
||||
|
||||
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||
should merge the branch `4.x` into the master branch regularly.
|
||||
- The branch `3.x` contains the legacy version `3.x`. Bugfixes are applied separately (if needed). The branch will not
|
||||
be merged with any of the other branches.
|
||||
- The branch `master` contains the current development version (v5).
|
||||
- The branch `4.x` contains the previous stable version. Only critical bugfixes are backported there.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
@@ -38,21 +35,13 @@ Generally we like to see pull requests that
|
||||
|
||||
## Building
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
- Node.js
|
||||
- [Grunt](http://gruntjs.com/getting-started)
|
||||
To build Handlebars.js you'll need Node.js installed.
|
||||
|
||||
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
|
||||
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
To build Handlebars.js from scratch, run `npm run build` in the root of the project. That will compile CJS modules via SWC and bundle UMD distributions via rspack, outputting results to the dist/ folder. To run tests, use `npm test`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||
@@ -81,21 +70,16 @@ npm test
|
||||
|
||||
## Linting and Formatting
|
||||
|
||||
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
|
||||
We do linting and formatting in two phases:
|
||||
|
||||
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||
while warnings are allowed.
|
||||
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||
are forbidden.
|
||||
Handlebars uses `oxlint` for linting, `oxfmt` for formatting, and `eslint` (with `eslint-plugin-compat`) for browser API compatibility checks.
|
||||
Committed files are linted and formatted in a pre-commit hook.
|
||||
|
||||
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 format** will run `prettier` on all files
|
||||
- **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 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).
|
||||
- **npm run lint** will run all linters and fail on warnings
|
||||
- **npm run format** will format all files
|
||||
- **npm run check-before-pull-request** will perform all checks that our CI job does, excluding integration tests.
|
||||
- **npm run test:integration** will run integration tests (bundler compatibility with webpack, rollup, etc.)
|
||||
These tests only work on Linux.
|
||||
|
||||
## Releasing the latest version
|
||||
|
||||
@@ -113,12 +97,8 @@ A full release may be completed with the following:
|
||||
|
||||
```
|
||||
npm ci
|
||||
npx grunt
|
||||
npm run build
|
||||
npm publish
|
||||
|
||||
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
|
||||
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
/* eslint-disable no-process-env */
|
||||
module.exports = function (grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
clean: ['tmp', 'dist', 'tests/integration/**/node_modules'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function (content) {
|
||||
return (
|
||||
grunt.template.process(
|
||||
'/**!\n\n @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||
) +
|
||||
content +
|
||||
'\n// @license-end\n'
|
||||
);
|
||||
},
|
||||
},
|
||||
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }],
|
||||
},
|
||||
components: {
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'components/',
|
||||
src: ['**'],
|
||||
dest: 'dist/components',
|
||||
},
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'dist/',
|
||||
src: ['*.js'],
|
||||
dest: 'dist/components',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMaps: 'inline',
|
||||
loose: ['es6.modules'],
|
||||
auxiliaryCommentBefore: 'istanbul ignore next',
|
||||
},
|
||||
cjs: {
|
||||
files: [
|
||||
{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/cjs/',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
webpack: {
|
||||
options: {
|
||||
context: __dirname,
|
||||
output: {
|
||||
path: 'dist/',
|
||||
library: 'Handlebars',
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
},
|
||||
handlebars: {
|
||||
entry: './dist/cjs/handlebars.js',
|
||||
output: {
|
||||
filename: 'handlebars.js',
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
entry: './dist/cjs/handlebars.runtime.js',
|
||||
output: {
|
||||
filename: 'handlebars.runtime.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/,
|
||||
},
|
||||
dist: {
|
||||
files: [
|
||||
{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function (dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
concat: {
|
||||
tests: {
|
||||
src: ['spec/!(require).js'],
|
||||
dest: 'tmp/tests.js',
|
||||
},
|
||||
},
|
||||
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
base: '.',
|
||||
hostname: '*',
|
||||
port: 9999,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
shell: {
|
||||
integrationTests: {
|
||||
command: './tests/integration/run-integration-tests.sh',
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true,
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['on-file-change'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('node', ['babel:cjs']);
|
||||
grunt.registerTask('globals', ['webpack']);
|
||||
grunt.registerTask('release', 'Build final packages', [
|
||||
'uglify',
|
||||
'test:min',
|
||||
'copy:dist',
|
||||
'copy:components',
|
||||
]);
|
||||
|
||||
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
|
||||
|
||||
// === Primary tasks ===
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
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',
|
||||
['node', 'globals']
|
||||
);
|
||||
grunt.registerTask('integration-tests', [
|
||||
'default',
|
||||
'shell:integrationTests',
|
||||
]);
|
||||
};
|
||||
+95
-65
@@ -5,8 +5,7 @@
|
||||
[](https://bundlephobia.com/package/handlebars)
|
||||
[](https://packagephobia.com/result?p=handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
# Handlebars.js
|
||||
|
||||
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||
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.
|
||||
@@ -14,13 +13,12 @@ Handlebars is largely compatible with Mustache templates. In most cases it is po
|
||||
Checkout the official Handlebars docs site at
|
||||
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||
|
||||
Installing
|
||||
----------
|
||||
## Installing
|
||||
|
||||
See our [installation documentation](https://handlebarsjs.com/installation/).
|
||||
See our [installation documentation](https://handlebarsjs.com/guide/installation/).
|
||||
|
||||
## Usage
|
||||
|
||||
Usage
|
||||
-----
|
||||
In general, the syntax of Handlebars.js templates is a superset
|
||||
of Mustache templates. For basic syntax, check out the [Mustache
|
||||
manpage](https://mustache.github.io/mustache.5.html).
|
||||
@@ -30,13 +28,20 @@ the template into a function. The generated function takes a context
|
||||
argument, which will be used to render the template.
|
||||
|
||||
```js
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids.length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var source =
|
||||
'<p>Hello, my name is {{name}}. I am from {{hometown}}. I have ' +
|
||||
'{{kids.length}} kids:</p>' +
|
||||
'<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>';
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "name": "Alan", "hometown": "Somewhere, TX",
|
||||
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
|
||||
var data = {
|
||||
name: 'Alan',
|
||||
hometown: 'Somewhere, TX',
|
||||
kids: [
|
||||
{ name: 'Jimmy', age: '12' },
|
||||
{ name: 'Sally', age: '4' },
|
||||
],
|
||||
};
|
||||
var result = template(data);
|
||||
|
||||
// Would render:
|
||||
@@ -49,13 +54,12 @@ var result = template(data);
|
||||
|
||||
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
## Precompiling Templates
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
|
||||
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/guide/installation/precompilation.html).
|
||||
|
||||
## Differences Between Handlebars.js and Mustache
|
||||
|
||||
Differences Between Handlebars.js and Mustache
|
||||
----------------------------------------------
|
||||
Handlebars.js adds a couple of additional features to make writing
|
||||
templates easier and also changes a tiny detail of how partials work.
|
||||
|
||||
@@ -70,14 +74,13 @@ Block expressions have the same syntax as mustache sections but should not be co
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||
- Handlebars does not allow space between the opening `{{` and a command character such as `#`, `/` or `>`. The command character must immediately follow the braces, so for example `{{> partial }}` is allowed but `{{ > partial }}` is not.
|
||||
- Alternative delimiters are not supported.
|
||||
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
## Supported Environments
|
||||
|
||||
Handlebars has been designed to work in any ECMAScript 2020 environment. This includes
|
||||
|
||||
@@ -89,9 +92,7 @@ Handlebars has been designed to work in any ECMAScript 2020 environment. This in
|
||||
|
||||
If you need to support older environments, use Handlebars version 4.
|
||||
|
||||
|
||||
Performance
|
||||
-----------
|
||||
## Performance
|
||||
|
||||
In a rough performance test, precompiled Handlebars.js templates (in
|
||||
the original version of Handlebars.js) rendered in about half the
|
||||
@@ -103,9 +104,44 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
### Benchmarks
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
The project includes a comprehensive benchmark suite (powered by [tinybench](https://github.com/tinylibs/tinybench)) that measures compilation, execution, precompilation, and end-to-end performance across templates of varying size and complexity.
|
||||
|
||||
```bash
|
||||
# Run benchmarks (auto-labels with current git branch)
|
||||
npm run bench
|
||||
|
||||
# Run with a custom label
|
||||
npm run bench -- --label my-optimization
|
||||
|
||||
# Filter templates by name (regex, case-insensitive)
|
||||
npm run bench -- --grep "complex|recursive"
|
||||
|
||||
# Run only specific sections (regex, case-insensitive)
|
||||
npm run bench -- --section precompil
|
||||
npm run bench -- --section "compilation|precompil"
|
||||
|
||||
# Compare results
|
||||
npm run bench:compare
|
||||
|
||||
# Or specify files explicitly
|
||||
npm run bench:compare -- bench/results/bench-*-main.md bench/results/bench-*-feat.md
|
||||
```
|
||||
|
||||
Results are saved as timestamped Markdown files in `bench/results/`. Each report includes ops/sec, avg latency, p50/p75/p99 percentiles, and sample counts.
|
||||
|
||||
Typical workflow for comparing branches:
|
||||
|
||||
```bash
|
||||
git checkout main && npm run bench
|
||||
git checkout my-feature && npm run bench
|
||||
npm run bench:compare
|
||||
```
|
||||
|
||||
When run without arguments, `bench:compare` auto-selects two result files: if a file labelled "main" exists it is always used as the baseline, otherwise the older file is the baseline. The comparison uses p75 latency for the diff to filter outliers, and marks changes with `!` (>2%) and `!!` (>5%).
|
||||
|
||||
## Upgrading
|
||||
|
||||
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
|
||||
@@ -114,55 +150,49 @@ If you are using Handlebars in production, please regularly look for issues labe
|
||||
If this label is applied to an issue, it means that the requested change is probably not a breaking change,
|
||||
but since Handlebars is widely in use by a lot of people, there's always a chance that it breaks somebody's build.
|
||||
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
## Known Issues
|
||||
|
||||
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
|
||||
----------------------
|
||||
- [apiDoc](https://github.com/apidoc/apidoc) apiDoc uses handlebars as parsing engine for api documentation view generation.
|
||||
- [Assemble](https://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js as its template engine.
|
||||
- [CoSchedule](https://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js.
|
||||
- [Ember.js](https://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
|
||||
- [express-handlebars](https://github.com/express-handlebars/express-handlebars) A Handlebars view engine for Express which doesn't suck.
|
||||
- [express-hbs](https://github.com/TryGhost/express-hbs) Express Handlebars template engine with inheritance, partials, i18n and async helpers.
|
||||
- [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
- [handlebars-action](https://github.com/marketplace/actions/handlebars-action) A GitHub action to transform files in your repository with Handlebars templating.
|
||||
- [handlebars_assets](https://github.com/leshill/handlebars_assets) A Rails Asset Pipeline gem from Les Hill (@leshill).
|
||||
- [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library with 100+ handlebars helpers.
|
||||
- [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extensible and embeddable layout blocks as seen in other popular templating languages.
|
||||
- [handlebars-loader](https://github.com/pcardune/handlebars-loader) A handlebars template loader for webpack.
|
||||
- [handlebars-wax](https://github.com/shannonmoeller/handlebars-wax) The missing Handlebars API. Effortless registration of data, partials, helpers, and decorators using file-system globs, modules, and plain-old JavaScript objects.
|
||||
- [hbs](https://github.com/pillarjs/hbs) An Express.js view engine adapter for Handlebars.js, from Don Park.
|
||||
- [html-bundler-webpack-plugin](https://github.com/webdiscus/html-bundler-webpack-plugin) The webpack plugin to compile templates, [supports Handlebars](https://github.com/webdiscus/html-bundler-webpack-plugin#using-template-handlebars).
|
||||
- [incremental-bars](https://github.com/atomictag/incremental-bars) adds support for [incremental-dom](https://github.com/google/incremental-dom) as template target to Handlebars.
|
||||
- [jQuery plugin](https://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/).
|
||||
- [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||
- [koa-hbs](https://github.com/jwilm/koa-hbs) [koa](https://github.com/koajs/koa) generator based renderer for Handlebars.js.
|
||||
- [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
- [openVALIDATION](https://github.com/openvalidation/openvalidation) a natural language compiler for validation rules. Generates program code in Java, JavaScript, C#, Python and Rust with handlebars.
|
||||
- [Plop](https://plopjs.com/) is a micro-generator framework that makes it easy to create files with a level of uniformity.
|
||||
- [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
- [sammy.js](https://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
- [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
- [SproutCore](https://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
|
||||
- [vite-plugin-handlebars](https://github.com/alexlafroscia/vite-plugin-handlebars) A package for Vite 2. Allows for running your HTML files through the Handlebars compiler.
|
||||
- [YUI](https://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars.
|
||||
|
||||
* [apiDoc](https://github.com/apidoc/apidoc) apiDoc uses handlebars as parsing engine for api documentation view generation.
|
||||
* [Assemble](https://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert) and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js as its template engine.
|
||||
* [CoSchedule](https://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js.
|
||||
* [Ember.js](https://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
|
||||
* [express-handlebars](https://github.com/express-handlebars/express-handlebars) A Handlebars view engine for Express which doesn't suck.
|
||||
* [express-hbs](https://github.com/TryGhost/express-hbs) Express Handlebars template engine with inheritance, partials, i18n and async helpers.
|
||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
* [handlebars-action](https://github.com/marketplace/actions/handlebars-action) A GitHub action to transform files in your repository with Handlebars templating.
|
||||
* [handlebars_assets](https://github.com/leshill/handlebars_assets) A Rails Asset Pipeline gem from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library with 100+ handlebars helpers.
|
||||
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extensible and embeddable layout blocks as seen in other popular templating languages.
|
||||
* [handlebars-loader](https://github.com/pcardune/handlebars-loader) A handlebars template loader for webpack.
|
||||
* [handlebars-wax](https://github.com/shannonmoeller/handlebars-wax) The missing Handlebars API. Effortless registration of data, partials, helpers, and decorators using file-system globs, modules, and plain-old JavaScript objects.
|
||||
* [hbs](https://github.com/pillarjs/hbs) An Express.js view engine adapter for Handlebars.js, from Don Park.
|
||||
* [html-bundler-webpack-plugin](https://github.com/webdiscus/html-bundler-webpack-plugin) The webpack plugin to compile templates, [supports Handlebars](https://github.com/webdiscus/html-bundler-webpack-plugin#using-template-handlebars).
|
||||
* [incremental-bars](https://github.com/atomictag/incremental-bars) adds support for [incremental-dom](https://github.com/google/incremental-dom) as template target to Handlebars.
|
||||
* [jblotus](https://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would like to try out Handlebars.js in their browser.
|
||||
* [jQuery plugin](https://71104.github.io/jquery-handlebars/) allows you to use Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs) [koa](https://github.com/koajs/koa) generator based renderer for Handlebars.js.
|
||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
* [openVALIDATION](https://github.com/openvalidation/openvalidation) a natural language compiler for validation rules. Generates program code in Java, JavaScript, C#, Python and Rust with handlebars.
|
||||
* [Plop](https://plopjs.com/) is a micro-generator framework that makes it easy to create files with a level of uniformity.
|
||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
* [sammy.js](https://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [SproutCore](https://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
|
||||
* [vite-plugin-handlebars](https://github.com/alexlafroscia/vite-plugin-handlebars) A package for Vite 2. Allows for running your HTML files through the Handlebars compiler.
|
||||
* [YUI](https://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars.
|
||||
## External Resources
|
||||
|
||||
External Resources
|
||||
------------------
|
||||
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
- [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request][pull-request]!
|
||||
|
||||
License
|
||||
-------
|
||||
## License
|
||||
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||
Executable → Regular
+16
-31
@@ -1,7 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const yargs = require('yargs')
|
||||
import { loadTemplates, cli } from '../lib/precompiler.js';
|
||||
import yargs from 'yargs';
|
||||
|
||||
const parser = yargs(process.argv.slice(2))
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...')
|
||||
.help(false)
|
||||
.version(false)
|
||||
.option('f', {
|
||||
type: 'string',
|
||||
description: 'Output File',
|
||||
@@ -11,31 +16,6 @@ const yargs = require('yargs')
|
||||
type: 'string',
|
||||
description: 'Source Map File',
|
||||
})
|
||||
.option('esm', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exports ECMAScript module style, path to Handlebars module (eg. "handlebars/lib/handlebars")',
|
||||
default: null,
|
||||
})
|
||||
.option('c', {
|
||||
type: 'string',
|
||||
description: 'Exports CommonJS style, path to Handlebars module',
|
||||
alias: 'commonjs',
|
||||
default: null,
|
||||
})
|
||||
.option('a', {
|
||||
type: 'boolean',
|
||||
description: 'Exports AMD style (require.js)',
|
||||
alias: 'amd',
|
||||
deprecated: true,
|
||||
})
|
||||
.option('h', {
|
||||
type: 'string',
|
||||
description: 'Path to handlebar.js (only valid for AMD-style)',
|
||||
alias: 'handlebarPath',
|
||||
default: '',
|
||||
deprecated: true,
|
||||
})
|
||||
.option('k', {
|
||||
type: 'string',
|
||||
description: 'Known helpers',
|
||||
@@ -113,19 +93,24 @@ const yargs = require('yargs')
|
||||
})
|
||||
.wrap(120);
|
||||
|
||||
const argv = yargs.argv;
|
||||
const argv = parser.parseSync();
|
||||
argv.files = argv._;
|
||||
delete argv._;
|
||||
|
||||
const Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv, function (err, opts) {
|
||||
loadTemplates(argv, function (err, opts) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||
yargs.showHelp();
|
||||
parser.showHelp('log');
|
||||
} else {
|
||||
Precompiler.cli(opts);
|
||||
// cli() is async (returns a Promise), so errors would become unhandled
|
||||
// rejections. Re-throw via nextTick to surface them as uncaught exceptions.
|
||||
Promise.resolve(cli(opts)).catch((error) => {
|
||||
process.nextTick(() => {
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -297,7 +297,6 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
|
||||
- `nameLookup(parent, name, type)`
|
||||
Used to generate the code to resolve a given path component.
|
||||
|
||||
- `parent` is the existing code in the path resolution
|
||||
- `name` is the current path component
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||
@@ -312,7 +311,6 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
|
||||
- `appendToBuffer(source, location, explicit)`
|
||||
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||
|
||||
- `source` is the source code whose result is to be appending
|
||||
- `location` is the location of the source in the source map.
|
||||
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import compat from 'eslint-plugin-compat';
|
||||
|
||||
export default [
|
||||
{
|
||||
// Ignore everything except lib/
|
||||
ignores: ['**', '!lib/**'],
|
||||
},
|
||||
{
|
||||
// Only check browser API compat in the runtime library code.
|
||||
// All other linting is handled by oxlint.
|
||||
...compat.configs['flat/recommended'],
|
||||
files: ['lib/**/*.js'],
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,127 @@
|
||||
import Handlebars from './lib/index.js';
|
||||
import fs from 'fs';
|
||||
|
||||
// Read the basic.js test file and extract test cases
|
||||
// For now, manually define a set of representative test cases
|
||||
const tests = [];
|
||||
|
||||
function T(template, input, expected, opts) {
|
||||
tests.push({
|
||||
template,
|
||||
input: input ?? {},
|
||||
expected,
|
||||
compileOpts: opts?.compile ?? {},
|
||||
runtimeOpts: opts?.runtime ?? {},
|
||||
});
|
||||
}
|
||||
|
||||
// ============ basic.js equivalents ============
|
||||
const g = (t, i, e, co, ro) => T(t, i, e, { compile: co, runtime: ro });
|
||||
|
||||
g('{{foo}}', { foo: 'foo' }, 'foo');
|
||||
g('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
||||
g('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
||||
g('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
||||
g('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
||||
g(
|
||||
'Goodbye\n{{cruel}}\n{{world}}!',
|
||||
{ cruel: 'cruel', world: 'world' },
|
||||
'Goodbye\ncruel\nworld!'
|
||||
);
|
||||
g('{{.}}{{length}}', 'bye', 'bye3');
|
||||
g('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
||||
g(
|
||||
'{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
||||
{ cruel: 'cruel', world: 'world' },
|
||||
'Goodbye\ncruel\nworld!'
|
||||
);
|
||||
g(' {{~! comment ~}} blah', {}, 'blah');
|
||||
g(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
||||
g(' {{! comment ~}} blah', {}, ' blah');
|
||||
g(' {{~! comment}} blah', {}, ' blah');
|
||||
g('{{#if foo}}foo{{/if}}', { foo: true }, 'foo');
|
||||
g('{{#if foo}}foo{{else}}bar{{/if}}', { foo: false }, 'bar');
|
||||
g('{{#unless foo}}bar{{/unless}}', { foo: false }, 'bar');
|
||||
g('{{#with foo}}{{bar}}{{/with}}', { foo: { bar: 'baz' } }, 'baz');
|
||||
g('{{#each items}}{{this}}{{/each}}', { items: ['a', 'b', 'c'] }, 'abc');
|
||||
g('{{#each items}}{{@index}}{{/each}}', { items: ['a', 'b'] }, '01');
|
||||
g('<b>{{foo}}</b>', { foo: '&' }, '<b>&</b>');
|
||||
g('{{{foo}}}', { foo: '<b>' }, '<b>');
|
||||
g('{{foo.bar}}', { foo: { bar: 'baz' } }, 'baz');
|
||||
g('{{foo/bar}}', { foo: { bar: 'baz' } }, 'baz');
|
||||
g('{{../foo}}', {}, ''); // depth at root = empty
|
||||
g('{{{foo}}}', { foo: '&' }, '&');
|
||||
|
||||
// ============ blocks.js equivalents ============
|
||||
g(
|
||||
'{{#list people}}{{firstName}} {{lastName}}\n{{/list}}',
|
||||
{
|
||||
people: [
|
||||
{ firstName: 'Yehuda', lastName: 'Katz' },
|
||||
{ firstName: 'Carl', lastName: 'Lerche' },
|
||||
],
|
||||
},
|
||||
'Yehuda Katz\nCarl Lerche\n'
|
||||
);
|
||||
g(
|
||||
'{{#list people}}{{../prefix}} {{firstName}}\n{{/list}}',
|
||||
{ people: [{ firstName: 'Yehuda' }, { firstName: 'Carl' }], prefix: 'Mr' },
|
||||
'Mr Yehuda\nMr Carl\n'
|
||||
);
|
||||
g(
|
||||
'{{#each people as |person|}}{{person}}\n{{/each}}',
|
||||
{ people: ['Yehuda', 'Carl'] },
|
||||
'Yehuda\nCarl\n'
|
||||
);
|
||||
|
||||
// ============ builtins.js equivalents ============
|
||||
g(
|
||||
'{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!',
|
||||
{ goodbye: true, world: 'world' },
|
||||
'GOODBYE cruel world!'
|
||||
);
|
||||
g(
|
||||
'{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!',
|
||||
{ goodbye: false, world: 'world' },
|
||||
'cruel world!'
|
||||
);
|
||||
g('{{lookup foo "bar"}}', { foo: { bar: 'val' } }, 'val');
|
||||
g(
|
||||
'{{#each items as |value key|}}{{key}}:{{value}},{{/each}}',
|
||||
{ items: { a: 1, b: 2 } },
|
||||
'a:1,b:2,'
|
||||
);
|
||||
|
||||
// Write output
|
||||
const results = tests.map((t, i) => {
|
||||
let result,
|
||||
error = null;
|
||||
try {
|
||||
const env = Handlebars.create();
|
||||
const compiled = env.compile(t.template, t.compileOpts);
|
||||
result = compiled(t.input, t.runtimeOpts);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
result = null;
|
||||
}
|
||||
return {
|
||||
i,
|
||||
template: t.template,
|
||||
input: JSON.stringify(t.input),
|
||||
expected: t.expected,
|
||||
result,
|
||||
error,
|
||||
};
|
||||
});
|
||||
|
||||
fs.writeFileSync('/tmp/golden_results.json', JSON.stringify(results, null, 2));
|
||||
console.log(`Generated ${results.length} golden results`);
|
||||
const failures = results.filter((r) => r.result !== r.expected);
|
||||
if (failures.length) {
|
||||
console.log(`\nFAILURES (${failures.length}):`);
|
||||
failures.forEach((f) =>
|
||||
console.log(
|
||||
` [${f.i}] "${f.template}" expected="${f.expected}" got="${f.result}" error="${f.error}"`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/* eslint-env node */
|
||||
module.exports = {
|
||||
env: {
|
||||
// Handlebars should run natively in the browser
|
||||
node: false,
|
||||
},
|
||||
};
|
||||
+9
-5
@@ -5,14 +5,18 @@ import {
|
||||
Visitor,
|
||||
} from '@handlebars/parser';
|
||||
|
||||
import runtime from './handlebars.runtime';
|
||||
import runtime from './handlebars.runtime.js';
|
||||
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import AST from './handlebars/compiler/ast.js';
|
||||
import {
|
||||
Compiler,
|
||||
compile,
|
||||
precompile,
|
||||
} from './handlebars/compiler/compiler.js';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler.js';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
import noConflict from './handlebars/no-conflict.js';
|
||||
|
||||
let _create = runtime.create;
|
||||
function create() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as base from './handlebars/base';
|
||||
import * as base from './handlebars/base.js';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import * as Utils from './handlebars/utils';
|
||||
import * as runtime from './handlebars/runtime';
|
||||
// (This is done to easily share code between module systems and browser envs)
|
||||
import SafeString from './handlebars/safe-string.js';
|
||||
import * as Utils from './handlebars/utils.js';
|
||||
import * as runtime from './handlebars/runtime.js';
|
||||
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
import noConflict from './handlebars/no-conflict.js';
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
function create() {
|
||||
@@ -19,7 +19,10 @@ function create() {
|
||||
hb.Utils = Utils;
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
// Spread into a plain object so that runtime functions (e.g. checkRevision)
|
||||
// can be overridden by consumers. ES module namespace objects are sealed with
|
||||
// getter-only properties per spec, which would prevent monkey-patching.
|
||||
hb.VM = { ...runtime };
|
||||
hb.template = function (spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
@@ -34,4 +37,15 @@ noConflict(inst);
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
// Named re-exports for CJS interop.
|
||||
// See the comment in lib/index.js for the full explanation. In short:
|
||||
// require('handlebars/runtime').COMPILER_REVISION must be directly accessible
|
||||
// for tools like handlebars-loader that compare compiler and runtime revisions.
|
||||
export {
|
||||
VERSION,
|
||||
COMPILER_REVISION,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
} from './handlebars/base.js';
|
||||
|
||||
export default inst;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, extend, toString } from './utils';
|
||||
import { registerDefaultHelpers } from './helpers';
|
||||
import { registerDefaultDecorators } from './decorators';
|
||||
import logger from './logger';
|
||||
import { resetLoggedProperties } from './internal/proto-access';
|
||||
import { createFrame, extend, toString } from './utils.js';
|
||||
import { registerDefaultHelpers } from './helpers.js';
|
||||
import { registerDefaultDecorators } from './decorators.js';
|
||||
import logger from './logger.js';
|
||||
import { resetLoggedProperties } from './internal/proto-access.js';
|
||||
|
||||
export const VERSION = '4.7.7';
|
||||
export const COMPILER_REVISION = 8;
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
/* global define */
|
||||
import { isArray } from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
|
||||
try {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
// We don't support this in AMD environments. For these environments, we assume that
|
||||
// they are running on the browser and thus have no need for the source-map library.
|
||||
let SourceMap = require('source-map'); // eslint-disable-line no-undef
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
}
|
||||
} catch (err) {
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
|
||||
if (!SourceNode) {
|
||||
SourceNode = function (line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function (chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function () {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString: function () {
|
||||
return this.src;
|
||||
},
|
||||
};
|
||||
}
|
||||
import { isArray } from '../utils.js';
|
||||
import { SourceNode } from '#source-node';
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isArray, indexOf, extend } from '../utils';
|
||||
import AST from './ast';
|
||||
import { isArray, indexOf, extend } from '../utils.js';
|
||||
import AST from './ast.js';
|
||||
|
||||
const slice = [].slice;
|
||||
|
||||
@@ -74,7 +72,7 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
compileProgram: function (program) {
|
||||
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||
let childCompiler = new this.compiler(),
|
||||
result = childCompiler.compile(program, this.options),
|
||||
guid = this.guid++;
|
||||
|
||||
@@ -87,7 +85,7 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
accept: function (node) {
|
||||
/* istanbul ignore next: Sanity code */
|
||||
/* v8 ignore next -- Sanity code */
|
||||
if (!this[node.type]) {
|
||||
throw new Exception('Unknown type: ' + node.type, node);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
|
||||
import { isArray } from '../utils';
|
||||
import CodeGen from './code-gen';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from '../base.js';
|
||||
import { isArray } from '../utils.js';
|
||||
import CodeGen from './code-gen.js';
|
||||
|
||||
function Literal(value) {
|
||||
this.value = value;
|
||||
@@ -112,7 +112,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.source.currentLocation = firstLoc;
|
||||
this.pushSource('');
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
|
||||
throw new Exception('Compile completed with content left on stack');
|
||||
}
|
||||
@@ -158,7 +158,7 @@ JavaScriptCompiler.prototype = {
|
||||
};
|
||||
|
||||
if (this.decorators) {
|
||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
||||
ret.main_d = this.decorators;
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,9 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// Release AST/compiler references only needed during compilation for dedup
|
||||
this.context.environments.length = 0;
|
||||
|
||||
if (this.environment.usePartial) {
|
||||
ret.usePartial = true;
|
||||
}
|
||||
@@ -828,7 +831,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
for (let i = 0, l = children.length; i < l; i++) {
|
||||
child = children[i];
|
||||
compiler = new this.compiler(); // eslint-disable-line new-cap
|
||||
compiler = new this.compiler();
|
||||
|
||||
let existing = this.matchExistingProgram(child);
|
||||
|
||||
@@ -924,7 +927,7 @@ JavaScriptCompiler.prototype = {
|
||||
createdStack,
|
||||
usedLiteral;
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (!this.isInline()) {
|
||||
throw new Exception('replaceStack on non-inline');
|
||||
}
|
||||
@@ -972,7 +975,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.inlineStack = [];
|
||||
for (let i = 0, len = inlineStack.length; i < len; i++) {
|
||||
let entry = inlineStack[i];
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore next */
|
||||
if (entry instanceof Literal) {
|
||||
this.compileStack.push(entry);
|
||||
} else {
|
||||
@@ -994,7 +997,7 @@ JavaScriptCompiler.prototype = {
|
||||
return item.value;
|
||||
} else {
|
||||
if (!inline) {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (!this.stackSlot) {
|
||||
throw new Exception('Invalid stack pop');
|
||||
}
|
||||
@@ -1008,7 +1011,7 @@ JavaScriptCompiler.prototype = {
|
||||
let stack = this.isInline() ? this.inlineStack : this.compileStack,
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore next */
|
||||
if (item instanceof Literal) {
|
||||
return item.value;
|
||||
} else {
|
||||
@@ -1172,20 +1175,34 @@ function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
||||
stack = compiler.nameLookup(stack, parts[i], type);
|
||||
}
|
||||
|
||||
if (requireTerminal) {
|
||||
if (!requireTerminal) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
if (startPartIndex > len) {
|
||||
// Compat mode already consumed all parts via depthedLookup; the stack
|
||||
// holds the resolved value, not an object to look the property up on.
|
||||
// Use container.strictLookup to traverse depths with a strict error on miss.
|
||||
return [
|
||||
compiler.aliasable('container.strict'),
|
||||
'(',
|
||||
stack,
|
||||
', ',
|
||||
compiler.aliasable('container.strictLookup'),
|
||||
'(depths, ',
|
||||
compiler.quotedString(parts[len]),
|
||||
', ',
|
||||
JSON.stringify(compiler.source.currentLocation),
|
||||
' )',
|
||||
];
|
||||
} else {
|
||||
return stack;
|
||||
}
|
||||
|
||||
return [
|
||||
compiler.aliasable('container.strict'),
|
||||
'(',
|
||||
stack,
|
||||
', ',
|
||||
compiler.quotedString(parts[len]),
|
||||
', ',
|
||||
JSON.stringify(compiler.source.currentLocation),
|
||||
' )',
|
||||
];
|
||||
}
|
||||
|
||||
export default JavaScriptCompiler;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { isArray } from '../utils.js';
|
||||
|
||||
// Lightweight stub for browser environments where the source-map package
|
||||
// (which depends on Node.js built-ins) is not available.
|
||||
export function SourceNode(line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
}
|
||||
|
||||
SourceNode.prototype = {
|
||||
add(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap() {
|
||||
return { code: this.toString() };
|
||||
},
|
||||
toString() {
|
||||
return this.src;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SourceNode } from 'source-map';
|
||||
@@ -1,4 +1,4 @@
|
||||
import registerInline from './decorators/inline';
|
||||
import registerInline from './decorators/inline.js';
|
||||
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { extend } from '../utils';
|
||||
import { extend } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerDecorator(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing';
|
||||
import registerEach from './helpers/each';
|
||||
import registerHelperMissing from './helpers/helper-missing';
|
||||
import registerIf from './helpers/if';
|
||||
import registerLog from './helpers/log';
|
||||
import registerLookup from './helpers/lookup';
|
||||
import registerWith from './helpers/with';
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing.js';
|
||||
import registerEach from './helpers/each.js';
|
||||
import registerHelperMissing from './helpers/helper-missing.js';
|
||||
import registerIf from './helpers/if.js';
|
||||
import registerLog from './helpers/log.js';
|
||||
import registerLookup from './helpers/lookup.js';
|
||||
import registerWith from './helpers/with.js';
|
||||
|
||||
export function registerDefaultHelpers(instance) {
|
||||
registerBlockHelperMissing(instance);
|
||||
@@ -20,7 +20,8 @@ export function moveHelperToHooks(instance, helperName, keepHelper) {
|
||||
if (instance.helpers[helperName]) {
|
||||
instance.hooks[helperName] = instance.helpers[helperName];
|
||||
if (!keepHelper) {
|
||||
delete instance.helpers[helperName];
|
||||
// Using delete is slow
|
||||
instance.helpers[helperName] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isArray } from '../utils';
|
||||
import { isArray } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('blockHelperMissing', function (context, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { createFrame, isArray, isFunction } from '../utils';
|
||||
import { createFrame, isArray, isFunction, isMap, isSet } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('each', function (context, options) {
|
||||
@@ -21,7 +21,7 @@ export default function (instance) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
function execIteration(field, value, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
@@ -31,7 +31,7 @@ export default function (instance) {
|
||||
|
||||
ret =
|
||||
ret +
|
||||
fn(context[field], {
|
||||
fn(value, {
|
||||
data: data,
|
||||
blockParams: [context[field], field],
|
||||
});
|
||||
@@ -41,9 +41,19 @@ export default function (instance) {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
if (i in context) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
execIteration(i, context[i], i, i === context.length - 1);
|
||||
}
|
||||
}
|
||||
} else if (isMap(context)) {
|
||||
const j = context.size;
|
||||
for (const [key, value] of context) {
|
||||
execIteration(key, value, i++, i === j);
|
||||
}
|
||||
} else if (isSet(context)) {
|
||||
const j = context.size;
|
||||
for (const value of context) {
|
||||
execIteration(i, value, i++, i === j);
|
||||
}
|
||||
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||
const newContext = [];
|
||||
const iterator = context[Symbol.iterator]();
|
||||
@@ -52,7 +62,7 @@ export default function (instance) {
|
||||
}
|
||||
context = newContext;
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
execIteration(i, context[i], i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
@@ -62,13 +72,13 @@ export default function (instance) {
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an intermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1);
|
||||
execIteration(priorKey, context[priorKey], i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
});
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
execIteration(priorKey, context[priorKey], i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import { isEmpty, isFunction } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('if', function (conditional, options) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import { isEmpty, isFunction } from '../utils';
|
||||
import { isEmpty, isFunction } from '../utils.js';
|
||||
|
||||
export default function (instance) {
|
||||
instance.registerHelper('with', function (context, options) {
|
||||
|
||||
@@ -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,30 @@
|
||||
import { createNewLookupObject } from './create-new-lookup-object';
|
||||
import logger from '../logger';
|
||||
import { extend } from '../utils.js';
|
||||
import logger from '../logger.js';
|
||||
|
||||
const loggedProperties = Object.create(null);
|
||||
|
||||
export function createProtoAccessControl(runtimeOptions) {
|
||||
let defaultMethodWhiteList = Object.create(null);
|
||||
defaultMethodWhiteList['constructor'] = false;
|
||||
defaultMethodWhiteList['__defineGetter__'] = false;
|
||||
defaultMethodWhiteList['__defineSetter__'] = false;
|
||||
defaultMethodWhiteList['__lookupGetter__'] = false;
|
||||
|
||||
let defaultPropertyWhiteList = Object.create(null);
|
||||
// Create an object with "null"-prototype to avoid truthy results on
|
||||
// prototype properties.
|
||||
const propertyWhiteList = Object.create(null);
|
||||
// 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;
|
||||
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
|
||||
|
||||
return {
|
||||
properties: {
|
||||
whitelist: createNewLookupObject(
|
||||
defaultPropertyWhiteList,
|
||||
runtimeOptions.allowedProtoProperties
|
||||
),
|
||||
whitelist: propertyWhiteList,
|
||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault,
|
||||
},
|
||||
methods: {
|
||||
whitelist: createNewLookupObject(
|
||||
defaultMethodWhiteList,
|
||||
runtimeOptions.allowedProtoMethods
|
||||
),
|
||||
whitelist: methodWhiteList,
|
||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { indexOf } from './utils';
|
||||
import { indexOf } from './utils.js';
|
||||
|
||||
let logger = {
|
||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default function (Handlebars) {
|
||||
let $Handlebars = globalThis.Handlebars;
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
Handlebars.noConflict = function () {
|
||||
if (globalThis.Handlebars === Handlebars) {
|
||||
globalThis.Handlebars = $Handlebars;
|
||||
|
||||
+39
-23
@@ -1,17 +1,17 @@
|
||||
import { Exception } from '@handlebars/parser';
|
||||
import * as Utils from './utils';
|
||||
import * as Utils from './utils.js';
|
||||
import {
|
||||
COMPILER_REVISION,
|
||||
createFrame,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
} from './base';
|
||||
import { moveHelperToHooks } from './helpers';
|
||||
import { wrapHelper } from './internal/wrapHelper';
|
||||
} from './base.js';
|
||||
import { moveHelperToHooks } from './helpers.js';
|
||||
import { wrapHelper } from './internal/wrapHelper.js';
|
||||
import {
|
||||
createProtoAccessControl,
|
||||
resultIsAllowed,
|
||||
} from './internal/proto-access';
|
||||
} from './internal/proto-access.js';
|
||||
|
||||
export function checkRevision(compilerInfo) {
|
||||
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||
@@ -47,7 +47,7 @@ export function checkRevision(compilerInfo) {
|
||||
}
|
||||
|
||||
export function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (!env) {
|
||||
throw new Exception('No environment passed to template');
|
||||
}
|
||||
@@ -71,17 +71,10 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
|
||||
let extendedOptions = Utils.extend({}, options, {
|
||||
hooks: this.hooks,
|
||||
protoAccessControl: this.protoAccessControl,
|
||||
});
|
||||
options.hooks = this.hooks;
|
||||
options.protoAccessControl = this.protoAccessControl;
|
||||
|
||||
let result = env.VM.invokePartial.call(
|
||||
this,
|
||||
partial,
|
||||
context,
|
||||
extendedOptions
|
||||
);
|
||||
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
options.partials[options.name] = env.compile(
|
||||
@@ -89,7 +82,7 @@ export function template(templateSpec, env) {
|
||||
templateSpec.compilerOptions,
|
||||
env
|
||||
);
|
||||
result = options.partials[options.name](context, extendedOptions);
|
||||
result = options.partials[options.name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
if (options.indent) {
|
||||
@@ -123,7 +116,27 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return container.lookupProperty(obj, name);
|
||||
},
|
||||
strictLookup: function (depths, name, loc) {
|
||||
const len = depths.length;
|
||||
let depth;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const d = depths[i];
|
||||
if (
|
||||
d &&
|
||||
(typeof d === 'object' || typeof d === 'function') &&
|
||||
name in d
|
||||
) {
|
||||
depth = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return container.strict(depth, name, loc);
|
||||
},
|
||||
lookupProperty: function (parent, propertyName) {
|
||||
if (Utils.isMap(parent)) {
|
||||
return parent.get(propertyName);
|
||||
}
|
||||
|
||||
let result = parent[propertyName];
|
||||
if (result == null) {
|
||||
return result;
|
||||
@@ -251,8 +264,9 @@ export function template(templateSpec, env) {
|
||||
|
||||
function _setup(options) {
|
||||
if (!options.partial) {
|
||||
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
||||
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
||||
let mergedHelpers = {};
|
||||
addHelpers(mergedHelpers, env.helpers, container);
|
||||
addHelpers(mergedHelpers, options.helpers, container);
|
||||
container.helpers = mergedHelpers;
|
||||
|
||||
if (templateSpec.usePartial) {
|
||||
@@ -413,9 +427,10 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||
return prog;
|
||||
}
|
||||
|
||||
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||
Object.keys(mergedHelpers).forEach((helperName) => {
|
||||
let helper = mergedHelpers[helperName];
|
||||
function addHelpers(mergedHelpers, helpers, container) {
|
||||
if (!helpers) return;
|
||||
Object.keys(helpers).forEach((helperName) => {
|
||||
let helper = helpers[helperName];
|
||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||
});
|
||||
}
|
||||
@@ -423,6 +438,7 @@ function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||
function passLookupPropertyOption(helper, container) {
|
||||
const lookupProperty = container.lookupProperty;
|
||||
return wrapHelper(helper, (options) => {
|
||||
return Utils.extend({ lookupProperty }, options);
|
||||
options.lookupProperty = lookupProperty;
|
||||
return options;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,14 +35,18 @@ export function isFunction(value) {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray =
|
||||
Array.isArray ||
|
||||
function (value) {
|
||||
function testTag(name) {
|
||||
const tag = '[object ' + name + ']';
|
||||
return function (value) {
|
||||
return value && typeof value === 'object'
|
||||
? toString.call(value) === '[object Array]'
|
||||
? toString.call(value) === tag
|
||||
: false;
|
||||
};
|
||||
}
|
||||
|
||||
export const isArray = Array.isArray;
|
||||
export const isMap = testTag('Map');
|
||||
export const isSet = testTag('Set');
|
||||
|
||||
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||
export function indexOf(array, value) {
|
||||
|
||||
+44
-24
@@ -1,26 +1,46 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-var */
|
||||
import handlebars from './handlebars.js';
|
||||
import { PrintVisitor, print } from '@handlebars/parser';
|
||||
|
||||
// var local = handlebars.create();
|
||||
handlebars.PrintVisitor = PrintVisitor;
|
||||
handlebars.print = print;
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||
|
||||
var parser = require('@handlebars/parser');
|
||||
handlebars.PrintVisitor = parser.PrintVisitor;
|
||||
handlebars.print = parser.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
function extension(module, filename) {
|
||||
var fs = require('fs');
|
||||
var templateString = fs.readFileSync(filename, 'utf8');
|
||||
module.exports = handlebars.compile(templateString);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
require.extensions['.handlebars'] = extension;
|
||||
require.extensions['.hbs'] = extension;
|
||||
}
|
||||
// Named exports for CJS interop.
|
||||
//
|
||||
// When Node.js (v22+) runs require() on an ESM module, it returns the module
|
||||
// namespace object — only named exports become direct properties. Without these,
|
||||
// require('handlebars') would return { default: inst } and calls like
|
||||
// Handlebars.precompile() or Handlebars.COMPILER_REVISION would be undefined.
|
||||
//
|
||||
// Tools like handlebars-loader rely on these being directly accessible via
|
||||
// require('handlebars').create(), require('handlebars').COMPILER_REVISION, etc.
|
||||
export const {
|
||||
create,
|
||||
compile,
|
||||
precompile,
|
||||
parse,
|
||||
parseWithoutProcessing,
|
||||
COMPILER_REVISION,
|
||||
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||
REVISION_CHANGES,
|
||||
VERSION,
|
||||
AST,
|
||||
Compiler,
|
||||
JavaScriptCompiler,
|
||||
Parser,
|
||||
Visitor,
|
||||
SafeString,
|
||||
Exception,
|
||||
Utils,
|
||||
escapeExpression,
|
||||
VM,
|
||||
template,
|
||||
log,
|
||||
registerHelper,
|
||||
unregisterHelper,
|
||||
registerPartial,
|
||||
unregisterPartial,
|
||||
registerDecorator,
|
||||
unregisterDecorator,
|
||||
} = handlebars;
|
||||
export { PrintVisitor, print };
|
||||
export default handlebars;
|
||||
|
||||
+22
-53
@@ -1,12 +1,11 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'neo-async';
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import Handlebars from './handlebars.js';
|
||||
import { basename } from 'path';
|
||||
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||
|
||||
module.exports.loadTemplates = function (opts, callback) {
|
||||
export function loadTemplates(opts, callback) {
|
||||
loadStrings(opts, function (err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@@ -21,7 +20,7 @@ module.exports.loadTemplates = function (opts, callback) {
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function loadStrings(opts, callback) {
|
||||
let strings = arrayCast(opts.string),
|
||||
@@ -95,7 +94,7 @@ function loadFiles(opts, callback) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdir(path, function (err, children) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -114,7 +113,7 @@ function loadFiles(opts, callback) {
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function (err, data) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
/* v8 ignore next -- Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -153,7 +152,7 @@ function loadFiles(opts, callback) {
|
||||
);
|
||||
}
|
||||
|
||||
module.exports.cli = function (opts) {
|
||||
export async function cli(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
@@ -177,12 +176,7 @@ module.exports.cli = function (opts) {
|
||||
}
|
||||
|
||||
// Force simple mode if we have only one template and it's unnamed.
|
||||
if (
|
||||
!opts.amd &&
|
||||
!opts.commonjs &&
|
||||
opts.templates.length === 1 &&
|
||||
!opts.templates[0].name
|
||||
) {
|
||||
if (opts.templates.length === 1 && !opts.templates[0].name) {
|
||||
opts.simple = true;
|
||||
}
|
||||
|
||||
@@ -201,19 +195,7 @@ module.exports.cli = function (opts) {
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.add(
|
||||
"define(['" +
|
||||
opts.handlebarPath +
|
||||
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||
);
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else if (opts.esm) {
|
||||
output.add(`import handlebars from "${opts.esm}";`);
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
output.add('(function() {\n');
|
||||
output.add(' var template = Handlebars.template, templates = ');
|
||||
if (opts.namespace) {
|
||||
output.add(opts.namespace);
|
||||
@@ -224,7 +206,7 @@ module.exports.cli = function (opts) {
|
||||
output.add('{};\n');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function (template) {
|
||||
for (const template of opts.templates) {
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o,
|
||||
@@ -241,11 +223,12 @@ module.exports.cli = function (opts) {
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
let consumer = await new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(
|
||||
precompiled.code,
|
||||
consumer
|
||||
);
|
||||
consumer.destroy();
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
@@ -255,9 +238,6 @@ module.exports.cli = function (opts) {
|
||||
throw new Handlebars.Exception('Name missing for template');
|
||||
}
|
||||
|
||||
if (opts.amd && !multiple) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add([
|
||||
objectName,
|
||||
"['",
|
||||
@@ -267,18 +247,11 @@ module.exports.cli = function (opts) {
|
||||
');\n',
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (multiple) {
|
||||
output.add(['return ', objectName, ';\n']);
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.add('})();');
|
||||
}
|
||||
output.add('})();');
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -289,7 +262,7 @@ module.exports.cli = function (opts) {
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = minify(output, opts.map);
|
||||
output = await minify(output, opts.map);
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -302,7 +275,7 @@ module.exports.cli = function (opts) {
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function arrayCast(value) {
|
||||
value = value != null ? value : [];
|
||||
@@ -315,29 +288,25 @@ function arrayCast(value) {
|
||||
/**
|
||||
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||
*
|
||||
* We are using `require` instead of `import` here, because es6-modules do not allow
|
||||
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
|
||||
* should not be a problem.
|
||||
*
|
||||
* @param {string} output the compiled template
|
||||
* @param {string} sourceMapFile the file to write the source map to.
|
||||
*/
|
||||
function minify(output, sourceMapFile) {
|
||||
async function minify(output, sourceMapFile) {
|
||||
let uglify;
|
||||
try {
|
||||
// Try to resolve uglify-js in order to see if it does exist
|
||||
require.resolve('uglify-js');
|
||||
uglify = await import('uglify-js');
|
||||
// Handle both default and named exports
|
||||
uglify = uglify.default || uglify;
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
// Something else seems to be wrong
|
||||
if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND') {
|
||||
throw e;
|
||||
}
|
||||
// it does not exist!
|
||||
console.error(
|
||||
'Code minimization is disabled due to missing uglify-js dependency'
|
||||
);
|
||||
return output;
|
||||
}
|
||||
return require('uglify-js').minify(output.code, {
|
||||
return uglify.minify(output.code, {
|
||||
sourceMap: {
|
||||
content: output.map,
|
||||
url: sourceMapFile,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
'check-coverage': true,
|
||||
branches: 100,
|
||||
lines: 100,
|
||||
functions: 100,
|
||||
statements: 100,
|
||||
exclude: ['**/spec/**'],
|
||||
reporter: 'html',
|
||||
};
|
||||
Generated
+13948
-20616
File diff suppressed because it is too large
Load Diff
+96
-103
@@ -2,125 +2,110 @@
|
||||
"name": "handlebars",
|
||||
"version": "5.0.0-alpha.1",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "https://handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"html",
|
||||
"mustache",
|
||||
"template",
|
||||
"html"
|
||||
"template"
|
||||
],
|
||||
"homepage": "https://handlebarsjs.com/",
|
||||
"license": "MIT",
|
||||
"author": "Yehuda Katz",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.markdown",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@handlebars/parser": "^2.1.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.6.1",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"uglify-js": "^3.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@definitelytyped/dtslint": "^0.0.100",
|
||||
"@playwright/test": "^1.36.1",
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-diff": "^1.0.1",
|
||||
"concurrently": "^5.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-prettier": "^8.9.0",
|
||||
"eslint-plugin-compat": "4.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"grunt": "^1.0.4",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-cli": "^1",
|
||||
"grunt-contrib-clean": "^1",
|
||||
"grunt-contrib-concat": "^1",
|
||||
"grunt-contrib-connect": "^3.0.0",
|
||||
"grunt-contrib-copy": "^1",
|
||||
"grunt-contrib-requirejs": "^1",
|
||||
"grunt-contrib-uglify": "^1",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-shell": "^4.0.0",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"husky": "^3.1.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mocha": "^5",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"nyc": "^14.1.1",
|
||||
"prettier": "^3.0.0",
|
||||
"semver": "^5.0.1",
|
||||
"sinon": "^7.5.0",
|
||||
"typescript": "^3.4.3",
|
||||
"uglify-js": "^3.1.4",
|
||||
"underscore": "^1.5.1",
|
||||
"webpack": "^1.12.6",
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"browser": "./dist/cjs/handlebars.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "grunt build",
|
||||
"release": "grunt release",
|
||||
"publish:aws": "npm run test:tasks && grunt && grunt publish-to-aws",
|
||||
"format": "prettier --write '**/*.{js,css,json,md}' && eslint --fix .",
|
||||
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||
"lint:eslint": "eslint --max-warnings 0 .",
|
||||
"lint:prettier": "prettier --check '**/*.js'",
|
||||
"lint:types": "dtslint types",
|
||||
"test": "npm run test:mocha",
|
||||
"test:mocha": "grunt build && grunt test",
|
||||
"test:tasks": "mocha tasks/tests/",
|
||||
"test:browser": "playwright test --config tests/browser/playwright.config.js",
|
||||
"test:integration": "grunt integration-tests",
|
||||
"test:serve": "grunt connect:server:keepalive",
|
||||
"--- combined tasks ---": "",
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||
},
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/cjs"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist/*.js",
|
||||
"dist/cjs/**/*.js",
|
||||
"lib",
|
||||
"release-notes.md",
|
||||
"runtime.js",
|
||||
"types/*.d.ts",
|
||||
"runtime.d.ts"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead",
|
||||
"not IE 11",
|
||||
"maintained node versions"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"imports": {
|
||||
"#source-node": {
|
||||
"node": "./lib/handlebars/compiler/source-node.node.js",
|
||||
"default": "./lib/handlebars/compiler/source-node.browser.js"
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./runtime": {
|
||||
"types": "./runtime.d.ts",
|
||||
"default": "./lib/handlebars.runtime.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "node --input-type=module -e \"import fs from 'fs';fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tmp',{recursive:true,force:true})\"",
|
||||
"build": "npm run clean && rspack build",
|
||||
"release": "npm run build",
|
||||
"publish:aws": "npm run build && npm run test:tasks && node tasks/publish-to-aws.js",
|
||||
"format": "oxfmt --write . && oxlint --fix .",
|
||||
"lint": "npm run lint:oxlint && npm run lint:format && npm run lint:types && npm run lint:compat",
|
||||
"lint:oxlint": "oxlint --max-warnings 0 .",
|
||||
"lint:format": "oxfmt --check .",
|
||||
"lint:types": "tstyche",
|
||||
"lint:compat": "eslint",
|
||||
"test": "npm run build && vitest run --project node --project tasks --project rspack --coverage",
|
||||
"test:browser": "vitest run --project browser",
|
||||
"test:unit": "vitest run --project node",
|
||||
"test:tasks": "vitest run --project tasks",
|
||||
"test:publish": "npm run build && vitest run --project publish",
|
||||
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
|
||||
"test:serve": "npx serve -l 9999 .",
|
||||
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
|
||||
"bench": "node tests/bench/perf.js",
|
||||
"bench:compare": "node tests/bench/compare.js",
|
||||
"bench:size": "node tests/bench/size.js",
|
||||
"--- combined tasks ---": "",
|
||||
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@handlebars/parser": "^2.2.2",
|
||||
"neo-async": "^2.6.2",
|
||||
"source-map": "^0.7.6",
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1011.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@rspack/cli": "^1.7.8",
|
||||
"@rspack/core": "^1.7.8",
|
||||
"@vitest/browser": "^4.0.18",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"cli-testlab": "^6.0.0",
|
||||
"concurrently": "^5.0.0",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-plugin-compat": "^7.0.1",
|
||||
"fs-extra": "^8.1.0",
|
||||
"husky": "^3.1.0",
|
||||
"lint-staged": "^16.3.2",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"oxfmt": "^0.36.0",
|
||||
"oxlint": "^1.51.0",
|
||||
"semver": "^5.0.1",
|
||||
"tinybench": "^6.0.0",
|
||||
"tstyche": "^6.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"uglify-js": "^3.19.3",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"uglify-js": "^3.19.3"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
@@ -128,12 +113,20 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,json,md}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
"oxfmt --write"
|
||||
],
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
"oxlint --fix"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead",
|
||||
"not IE 11",
|
||||
"maintained node versions"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'es5',
|
||||
};
|
||||
@@ -356,7 +356,6 @@ Breaking changes:
|
||||
Compatibility notes:
|
||||
|
||||
- Compiler revision increased - 06b7224
|
||||
|
||||
- This means that template compiled with versions prior to 4.3.0 will not work with runtimes >= 4.3.0
|
||||
The increase was done because the "helperMissing" and "blockHelperMissing" are now moved from the helpers
|
||||
to the internal "container.hooks" object, so old templates will not be able to call them anymore. We suggest
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { rspack } from '@rspack/core';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8')
|
||||
);
|
||||
const license = fs.readFileSync(path.resolve(__dirname, 'LICENSE'), 'utf8');
|
||||
const banner = `/*!
|
||||
|
||||
@license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
|
||||
${pkg.name} v${pkg.version}
|
||||
|
||||
${license}
|
||||
*/`;
|
||||
|
||||
function createConfig(entry, filename, minimize) {
|
||||
const plugins = [];
|
||||
|
||||
if (!minimize) {
|
||||
// For non-minified builds, use BannerPlugin to add the license header
|
||||
plugins.push(new rspack.BannerPlugin({ banner, raw: true }));
|
||||
}
|
||||
|
||||
return {
|
||||
mode: minimize ? 'production' : 'none',
|
||||
context: __dirname,
|
||||
entry,
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename,
|
||||
library: {
|
||||
name: 'Handlebars',
|
||||
type: 'self',
|
||||
export: 'default',
|
||||
},
|
||||
clean: false,
|
||||
},
|
||||
optimization: {
|
||||
minimize,
|
||||
minimizer: minimize
|
||||
? [
|
||||
new rspack.SwcJsMinimizerRspackPlugin({
|
||||
extractComments: false,
|
||||
minimizerOptions: {
|
||||
compress: { passes: 2 },
|
||||
mangle: true,
|
||||
format: {
|
||||
comments: false,
|
||||
// Prepend the license banner in the minified output
|
||||
preamble: banner,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
},
|
||||
plugins,
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
mainFields: ['main'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'builtin:swc-loader',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: { syntax: 'ecmascript' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
target: ['web', 'browserslist'],
|
||||
devtool: false,
|
||||
};
|
||||
}
|
||||
|
||||
export default [
|
||||
createConfig('./lib/handlebars.js', 'handlebars.js', false),
|
||||
createConfig('./lib/handlebars.runtime.js', 'handlebars.runtime.js', false),
|
||||
createConfig('./lib/handlebars.js', 'handlebars.min.js', true),
|
||||
createConfig(
|
||||
'./lib/handlebars.runtime.js',
|
||||
'handlebars.runtime.min.js',
|
||||
true
|
||||
),
|
||||
];
|
||||
Vendored
+3
-4
@@ -1,5 +1,4 @@
|
||||
import Handlebars = require('handlebars')
|
||||
import Handlebars from 'handlebars';
|
||||
|
||||
declare module "handlebars/runtime" {
|
||||
|
||||
}
|
||||
declare const runtime: typeof Handlebars;
|
||||
export default runtime;
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
|
||||
import runtime from './lib/handlebars.runtime.js';
|
||||
|
||||
export default runtime;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
CompilerContext: true,
|
||||
Handlebars: true,
|
||||
handlebarsEnv: true,
|
||||
shouldCompileTo: true,
|
||||
shouldCompileToWithPartials: true,
|
||||
shouldThrow: true,
|
||||
expectTemplate: true,
|
||||
compileWithPartials: true,
|
||||
suite: true,
|
||||
equal: true,
|
||||
equals: true,
|
||||
test: true,
|
||||
testBoth: true,
|
||||
raises: true,
|
||||
deepEqual: true,
|
||||
start: true,
|
||||
stop: true,
|
||||
ok: true,
|
||||
sinon: true,
|
||||
strictEqual: true,
|
||||
define: true,
|
||||
expect: true,
|
||||
chai: true,
|
||||
},
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
rules: {
|
||||
// Disabling for tests, for now.
|
||||
'no-path-concat': 'off',
|
||||
|
||||
'no-var': 'off',
|
||||
'dot-notation': 'off',
|
||||
},
|
||||
};
|
||||
+51
-55
@@ -7,102 +7,100 @@ describe('ast', function () {
|
||||
|
||||
describe('BlockStatement', function () {
|
||||
it('should throw on mustache mismatch', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
},
|
||||
Handlebars.Exception,
|
||||
"foo doesn't match bar - 2:5"
|
||||
);
|
||||
expect(function () {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
}).toThrow("foo doesn't match bar - 2:5");
|
||||
});
|
||||
});
|
||||
|
||||
describe('helpers', function () {
|
||||
describe('#helperExpression', function () {
|
||||
it('should handle mustache statements', function () {
|
||||
equals(
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: undefined,
|
||||
}),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [1],
|
||||
hash: undefined,
|
||||
}),
|
||||
true
|
||||
);
|
||||
equals(
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'MustacheStatement',
|
||||
params: [],
|
||||
hash: {},
|
||||
}),
|
||||
true
|
||||
);
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
it('should handle block statements', function () {
|
||||
equals(
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: undefined,
|
||||
}),
|
||||
false
|
||||
);
|
||||
equals(
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [1],
|
||||
hash: undefined,
|
||||
}),
|
||||
true
|
||||
);
|
||||
equals(
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
AST.helpers.helperExpression({
|
||||
type: 'BlockStatement',
|
||||
params: [],
|
||||
hash: {},
|
||||
}),
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
it('should handle subexpressions', function () {
|
||||
expect(AST.helpers.helperExpression({ type: 'SubExpression' })).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
it('should handle subexpressions', function () {
|
||||
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
|
||||
});
|
||||
it('should work with non-helper nodes', function () {
|
||||
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
|
||||
expect(AST.helpers.helperExpression({ type: 'Program' })).toBe(false);
|
||||
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'PartialStatement' }),
|
||||
expect(AST.helpers.helperExpression({ type: 'PartialStatement' })).toBe(
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'ContentStatement' }),
|
||||
expect(AST.helpers.helperExpression({ type: 'ContentStatement' })).toBe(
|
||||
false
|
||||
);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'CommentStatement' }),
|
||||
expect(AST.helpers.helperExpression({ type: 'CommentStatement' })).toBe(
|
||||
false
|
||||
);
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
|
||||
equals(
|
||||
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
|
||||
expect(AST.helpers.helperExpression({ type: 'PathExpression' })).toBe(
|
||||
false
|
||||
);
|
||||
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
|
||||
|
||||
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
|
||||
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
|
||||
expect(AST.helpers.helperExpression({ type: 'StringLiteral' })).toBe(
|
||||
false
|
||||
);
|
||||
expect(AST.helpers.helperExpression({ type: 'NumberLiteral' })).toBe(
|
||||
false
|
||||
);
|
||||
expect(AST.helpers.helperExpression({ type: 'BooleanLiteral' })).toBe(
|
||||
false
|
||||
);
|
||||
expect(AST.helpers.helperExpression({ type: 'UndefinedLiteral' })).toBe(
|
||||
false
|
||||
);
|
||||
expect(AST.helpers.helperExpression({ type: 'NullLiteral' })).toBe(
|
||||
false
|
||||
);
|
||||
|
||||
expect(AST.helpers.helperExpression({ type: 'Hash' })).toBe(false);
|
||||
expect(AST.helpers.helperExpression({ type: 'HashPair' })).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -111,13 +109,12 @@ describe('ast', function () {
|
||||
var ast, body;
|
||||
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
|
||||
equals(node.loc.start.line, firstLine);
|
||||
equals(node.loc.start.column, firstColumn);
|
||||
equals(node.loc.end.line, lastLine);
|
||||
equals(node.loc.end.column, lastColumn);
|
||||
expect(node.loc.start.line).toBe(firstLine);
|
||||
expect(node.loc.start.column).toBe(firstColumn);
|
||||
expect(node.loc.end.line).toBe(lastLine);
|
||||
expect(node.loc.end.column).toBe(lastColumn);
|
||||
}
|
||||
|
||||
/* eslint-disable no-multi-spaces */
|
||||
ast = Handlebars.parse(
|
||||
'line 1 {{line1Token}}\n' + // 1
|
||||
' line 2 {{line2token}}\n' + // 2
|
||||
@@ -131,7 +128,6 @@ describe('ast', function () {
|
||||
'{{else}}\n' + // 10
|
||||
'{{/open}}'
|
||||
); // 11
|
||||
/* eslint-enable no-multi-spaces */
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function () {
|
||||
|
||||
+7
-6
@@ -1,9 +1,3 @@
|
||||
global.handlebarsEnv = null;
|
||||
|
||||
beforeEach(function () {
|
||||
global.handlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
describe('basic context', function () {
|
||||
it('most basic', function () {
|
||||
expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo');
|
||||
@@ -387,6 +381,13 @@ describe('basic context', function () {
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
});
|
||||
|
||||
it('nested paths with Map', function () {
|
||||
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||
.withInput({ alan: new Map([['expression', 'beautiful']]) })
|
||||
.withMessage('Nested paths access nested objects')
|
||||
.toCompileTo('Goodbye beautiful world!');
|
||||
});
|
||||
|
||||
it('nested paths with empty string value', function () {
|
||||
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||
.withInput({ alan: { expression: '' } })
|
||||
|
||||
+22
-26
@@ -381,26 +381,26 @@ describe('blocks', function () {
|
||||
var run;
|
||||
expectTemplate('{{*decorator "success"}}')
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
equals(options.args[0], 'success');
|
||||
expect(options.args[0]).toBe('success');
|
||||
run = true;
|
||||
return fn;
|
||||
})
|
||||
.withInput({ foo: 'success' })
|
||||
.toCompileTo('');
|
||||
equals(run, true);
|
||||
expect(run).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail when accessing variables from root', function () {
|
||||
var run;
|
||||
expectTemplate('{{*decorator foo}}')
|
||||
.withDecorator('decorator', function (fn, props, container, options) {
|
||||
equals(options.args[0], undefined);
|
||||
expect(options.args[0]).toBeUndefined();
|
||||
run = true;
|
||||
return fn;
|
||||
})
|
||||
.withInput({ foo: 'fail' })
|
||||
.toCompileTo('');
|
||||
equals(run, true);
|
||||
expect(run).toBe(true);
|
||||
});
|
||||
|
||||
describe('registration', function () {
|
||||
@@ -411,9 +411,9 @@ describe('blocks', function () {
|
||||
return 'fail';
|
||||
});
|
||||
|
||||
equals(!!handlebarsEnv.decorators.foo, true);
|
||||
expect(handlebarsEnv.decorators.foo).toBeTruthy();
|
||||
handlebarsEnv.unregisterDecorator('foo');
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
expect(handlebarsEnv.decorators.foo).toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows multiple globals', function () {
|
||||
@@ -424,32 +424,28 @@ describe('blocks', function () {
|
||||
bar: function () {},
|
||||
});
|
||||
|
||||
equals(!!handlebarsEnv.decorators.foo, true);
|
||||
equals(!!handlebarsEnv.decorators.bar, true);
|
||||
expect(handlebarsEnv.decorators.foo).toBeTruthy();
|
||||
expect(handlebarsEnv.decorators.bar).toBeTruthy();
|
||||
handlebarsEnv.unregisterDecorator('foo');
|
||||
handlebarsEnv.unregisterDecorator('bar');
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
equals(handlebarsEnv.decorators.bar, undefined);
|
||||
expect(handlebarsEnv.decorators.foo).toBeUndefined();
|
||||
expect(handlebarsEnv.decorators.bar).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fails with multiple and args', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
handlebarsEnv.registerDecorator(
|
||||
{
|
||||
world: function () {
|
||||
return 'world!';
|
||||
},
|
||||
testHelper: function () {
|
||||
return 'found it!';
|
||||
},
|
||||
expect(function () {
|
||||
handlebarsEnv.registerDecorator(
|
||||
{
|
||||
world: function () {
|
||||
return 'world!';
|
||||
},
|
||||
{}
|
||||
);
|
||||
},
|
||||
Error,
|
||||
'Arg not supported with multiple decorators'
|
||||
);
|
||||
testHelper: function () {
|
||||
return 'found it!';
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
}).toThrow('Arg not supported with multiple decorators');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+69
-29
@@ -257,17 +257,13 @@ describe('builtin helpers', function () {
|
||||
// Object property iteration order is undefined according to ECMA spec,
|
||||
// so we need to check both possible orders
|
||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||
var actual = compileWithPartials(string, hash);
|
||||
var actual = CompilerContext.compile(string)(hash);
|
||||
var expected1 =
|
||||
'<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||
var expected2 =
|
||||
'2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||
|
||||
equals(
|
||||
actual === expected1 || actual === expected2,
|
||||
true,
|
||||
'each with object argument iterates over the contents when not empty'
|
||||
);
|
||||
expect([expected1, expected2]).toContain(actual);
|
||||
|
||||
expectTemplate(string)
|
||||
.withInput({
|
||||
@@ -508,6 +504,50 @@ describe('builtin helpers', function () {
|
||||
);
|
||||
});
|
||||
|
||||
it('each on Map', function () {
|
||||
var map = new Map([
|
||||
[1, 'one'],
|
||||
[2, 'two'],
|
||||
[3, 'three'],
|
||||
]);
|
||||
|
||||
expectTemplate('{{#each map}}{{@key}}(i{{@index}}) {{.}} {{/each}}')
|
||||
.withInput({ map: map })
|
||||
.toCompileTo('1(i0) one 2(i1) two 3(i2) three ');
|
||||
|
||||
expectTemplate('{{#each map}}{{#if @first}}{{.}}{{/if}}{{/each}}')
|
||||
.withInput({ map: map })
|
||||
.toCompileTo('one');
|
||||
|
||||
expectTemplate('{{#each map}}{{#if @last}}{{.}}{{/if}}{{/each}}')
|
||||
.withInput({ map: map })
|
||||
.toCompileTo('three');
|
||||
|
||||
expectTemplate('{{#each map}}{{.}}{{/each}}not-in-each')
|
||||
.withInput({ map: new Map() })
|
||||
.toCompileTo('not-in-each');
|
||||
});
|
||||
|
||||
it('each on Set', function () {
|
||||
var set = new Set([1, 2, 3]);
|
||||
|
||||
expectTemplate('{{#each set}}{{@key}}(i{{@index}}) {{.}} {{/each}}')
|
||||
.withInput({ set: set })
|
||||
.toCompileTo('0(i0) 1 1(i1) 2 2(i2) 3 ');
|
||||
|
||||
expectTemplate('{{#each set}}{{#if @first}}{{.}}{{/if}}{{/each}}')
|
||||
.withInput({ set: set })
|
||||
.toCompileTo('1');
|
||||
|
||||
expectTemplate('{{#each set}}{{#if @last}}{{.}}{{/if}}{{/each}}')
|
||||
.withInput({ set: set })
|
||||
.toCompileTo('3');
|
||||
|
||||
expectTemplate('{{#each set}}{{.}}{{/each}}not-in-each')
|
||||
.withInput({ set: new Set() })
|
||||
.toCompileTo('not-in-each');
|
||||
});
|
||||
|
||||
if (global.Symbol && global.Symbol.iterator) {
|
||||
it('each on iterable', function () {
|
||||
function Iterator(arr) {
|
||||
@@ -586,8 +626,8 @@ describe('builtin helpers', function () {
|
||||
.withInput({ blah: 'whee' })
|
||||
.withMessage('log should not display')
|
||||
.toCompileTo('');
|
||||
equals(1, levelArg, 'should call log with 1');
|
||||
equals('whee', logArg, "should call log with 'whee'");
|
||||
expect(levelArg).toBe(1);
|
||||
expect(logArg).toBe('whee');
|
||||
});
|
||||
|
||||
it('should call logger at data level', function () {
|
||||
@@ -602,21 +642,21 @@ describe('builtin helpers', function () {
|
||||
.withRuntimeOptions({ data: { level: '03' } })
|
||||
.withCompileOptions({ data: true })
|
||||
.toCompileTo('');
|
||||
equals('03', levelArg);
|
||||
equals('whee', logArg);
|
||||
expect(levelArg).toBe('03');
|
||||
expect(logArg).toBe('whee');
|
||||
});
|
||||
|
||||
it('should output to info', function () {
|
||||
var called;
|
||||
|
||||
console.info = function (info) {
|
||||
equals('whee', info);
|
||||
expect(info).toBe('whee');
|
||||
called = true;
|
||||
console.info = $info;
|
||||
console.log = $log;
|
||||
};
|
||||
console.log = function (log) {
|
||||
equals('whee', log);
|
||||
expect(log).toBe('whee');
|
||||
called = true;
|
||||
console.info = $info;
|
||||
console.log = $log;
|
||||
@@ -625,14 +665,14 @@ describe('builtin helpers', function () {
|
||||
expectTemplate('{{log blah}}')
|
||||
.withInput({ blah: 'whee' })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should log at data level', function () {
|
||||
var called;
|
||||
|
||||
console.error = function (log) {
|
||||
equals('whee', log);
|
||||
expect(log).toBe('whee');
|
||||
called = true;
|
||||
console.error = $error;
|
||||
};
|
||||
@@ -642,7 +682,7 @@ describe('builtin helpers', function () {
|
||||
.withRuntimeOptions({ data: { level: '03' } })
|
||||
.withCompileOptions({ data: true })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle missing logger', function () {
|
||||
@@ -650,7 +690,7 @@ describe('builtin helpers', function () {
|
||||
|
||||
console.error = undefined;
|
||||
console.log = function (log) {
|
||||
equals('whee', log);
|
||||
expect(log).toBe('whee');
|
||||
called = true;
|
||||
console.log = $log;
|
||||
};
|
||||
@@ -660,14 +700,14 @@ describe('builtin helpers', function () {
|
||||
.withRuntimeOptions({ data: { level: '03' } })
|
||||
.withCompileOptions({ data: true })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle string log levels', function () {
|
||||
var called;
|
||||
|
||||
console.error = function (log) {
|
||||
equals('whee', log);
|
||||
expect(log).toBe('whee');
|
||||
called = true;
|
||||
};
|
||||
|
||||
@@ -676,7 +716,7 @@ describe('builtin helpers', function () {
|
||||
.withRuntimeOptions({ data: { level: 'error' } })
|
||||
.withCompileOptions({ data: true })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
|
||||
called = false;
|
||||
|
||||
@@ -685,21 +725,21 @@ describe('builtin helpers', function () {
|
||||
.withRuntimeOptions({ data: { level: 'ERROR' } })
|
||||
.withCompileOptions({ data: true })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle hash log levels', function () {
|
||||
var called;
|
||||
|
||||
console.error = function (log) {
|
||||
equals('whee', log);
|
||||
expect(log).toBe('whee');
|
||||
called = true;
|
||||
};
|
||||
|
||||
expectTemplate('{{log blah level="error"}}')
|
||||
.withInput({ blah: 'whee' })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle hash log levels', function () {
|
||||
@@ -717,16 +757,16 @@ describe('builtin helpers', function () {
|
||||
expectTemplate('{{log blah level="debug"}}')
|
||||
.withInput({ blah: 'whee' })
|
||||
.toCompileTo('');
|
||||
equals(false, called);
|
||||
expect(called).toBe(false);
|
||||
});
|
||||
|
||||
it('should pass multiple log arguments', function () {
|
||||
var called;
|
||||
|
||||
console.info = console.log = function (log1, log2, log3) {
|
||||
equals('whee', log1);
|
||||
equals('foo', log2);
|
||||
equals(1, log3);
|
||||
expect(log1).toBe('whee');
|
||||
expect(log2).toBe('foo');
|
||||
expect(log3).toBe(1);
|
||||
called = true;
|
||||
console.log = $log;
|
||||
};
|
||||
@@ -734,20 +774,20 @@ describe('builtin helpers', function () {
|
||||
expectTemplate('{{log blah "foo" 1}}')
|
||||
.withInput({ blah: 'whee' })
|
||||
.toCompileTo('');
|
||||
equals(true, called);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it('should pass zero log arguments', function () {
|
||||
var called;
|
||||
|
||||
console.info = console.log = function () {
|
||||
expect(arguments.length).to.equal(0);
|
||||
expect(arguments.length).toBe(0);
|
||||
called = true;
|
||||
console.log = $log;
|
||||
};
|
||||
|
||||
expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo('');
|
||||
expect(called).to.be.true();
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
});
|
||||
|
||||
+65
-110
@@ -10,69 +10,56 @@ describe('compiler', function () {
|
||||
}
|
||||
|
||||
it('should treat as equal', function () {
|
||||
equal(compile('foo').equals(compile('foo')), true);
|
||||
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
||||
equal(
|
||||
expect(compile('foo').equals(compile('foo'))).toBe(true);
|
||||
expect(compile('{{foo}}').equals(compile('{{foo}}'))).toBe(true);
|
||||
expect(compile('{{foo.bar}}').equals(compile('{{foo.bar}}'))).toBe(true);
|
||||
expect(
|
||||
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
|
||||
compile('{{foo.bar baz "foo" true false bat=1}}')
|
||||
),
|
||||
true
|
||||
);
|
||||
equal(
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
compile('{{foo.bar (baz bat=1)}}').equals(
|
||||
compile('{{foo.bar (baz bat=1)}}')
|
||||
),
|
||||
true
|
||||
);
|
||||
equal(
|
||||
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
|
||||
true
|
||||
);
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}'))
|
||||
).toBe(true);
|
||||
});
|
||||
it('should treat as not equal', function () {
|
||||
equal(compile('foo').equals(compile('bar')), false);
|
||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
||||
equal(
|
||||
expect(compile('foo').equals(compile('bar'))).toBe(false);
|
||||
expect(compile('{{foo}}').equals(compile('{{bar}}'))).toBe(false);
|
||||
expect(compile('{{foo.bar}}').equals(compile('{{bar.bar}}'))).toBe(false);
|
||||
expect(
|
||||
compile('{{foo.bar baz bat=1}}').equals(
|
||||
compile('{{foo.bar bar bat=1}}')
|
||||
),
|
||||
false
|
||||
);
|
||||
equal(
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
compile('{{foo.bar (baz bat=1)}}').equals(
|
||||
compile('{{foo.bar (bar bat=1)}}')
|
||||
),
|
||||
false
|
||||
);
|
||||
equal(
|
||||
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
|
||||
false
|
||||
);
|
||||
equal(
|
||||
compile('{{#foo}} {{/foo}}').equals(
|
||||
compile('{{#foo}} {{foo}}{{/foo}}')
|
||||
),
|
||||
false
|
||||
);
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}'))
|
||||
).toBe(false);
|
||||
expect(
|
||||
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}'))
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compile', function () {
|
||||
it('should fail with invalid input', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile(null);
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.compile(null);
|
||||
}).toThrow(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile({});
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.compile({});
|
||||
}).toThrow(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
|
||||
);
|
||||
});
|
||||
@@ -80,71 +67,52 @@ describe('compiler', function () {
|
||||
it('should include the location in the error (row and column)', function () {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(
|
||||
true,
|
||||
false,
|
||||
'Statement must throw exception. This line should not be executed.'
|
||||
);
|
||||
expect.unreachable('Statement must throw exception');
|
||||
} catch (err) {
|
||||
equal(
|
||||
err.message,
|
||||
"if doesn't match def - 2:5",
|
||||
'Checking error message'
|
||||
);
|
||||
expect(err.message).toBe("if doesn't match def - 2:5");
|
||||
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
|
||||
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
|
||||
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
|
||||
// Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers.
|
||||
equal(err.column, 5, 'Checking error column');
|
||||
expect(err.column).toBe(5);
|
||||
}
|
||||
equal(err.lineNumber, 2, 'Checking error row');
|
||||
expect(err.lineNumber).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('should include the location as enumerable property', function () {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(
|
||||
true,
|
||||
false,
|
||||
'Statement must throw exception. This line should not be executed.'
|
||||
);
|
||||
expect.unreachable('Statement must throw exception');
|
||||
} catch (err) {
|
||||
equal(
|
||||
Object.prototype.propertyIsEnumerable.call(err, 'column'),
|
||||
true,
|
||||
'Checking error column'
|
||||
expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toBe(
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function () {
|
||||
equal(
|
||||
expect(
|
||||
Handlebars.compile({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }],
|
||||
})(),
|
||||
'Hello'
|
||||
);
|
||||
})()
|
||||
).toBe('Hello');
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function () {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
expect(Handlebars.compile('')()).toBe('');
|
||||
});
|
||||
|
||||
it('throws on desupported options', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile('Dudes', { trackIds: true });
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.compile('Dudes', { trackIds: true });
|
||||
}).toThrow(
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.compile('Dudes', { stringParams: true });
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.compile('Dudes', { stringParams: true });
|
||||
}).toThrow(
|
||||
'TrackIds and stringParams are no longer supported. See Github #1145'
|
||||
);
|
||||
});
|
||||
@@ -152,54 +120,41 @@ describe('compiler', function () {
|
||||
it('should not modify the options.data property(GH-1327)', function () {
|
||||
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(
|
||||
JSON.stringify(options, 0, 2),
|
||||
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
|
||||
);
|
||||
expect(options).toStrictEqual({ data: [{ a: 'foo' }, { a: 'bar' }] });
|
||||
});
|
||||
|
||||
it('should not modify the options.knownHelpers property(GH-1327)', function () {
|
||||
var options = { knownHelpers: {} };
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(
|
||||
JSON.stringify(options, 0, 2),
|
||||
JSON.stringify({ knownHelpers: {} }, 0, 2)
|
||||
);
|
||||
expect(options).toStrictEqual({ knownHelpers: {} });
|
||||
});
|
||||
});
|
||||
|
||||
describe('#precompile', function () {
|
||||
it('should fail with invalid input', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.precompile(null);
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.precompile(null);
|
||||
}).toThrow(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.precompile({});
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.precompile({});
|
||||
}).toThrow(
|
||||
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
|
||||
);
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function () {
|
||||
equal(
|
||||
/return "Hello"/.test(
|
||||
Handlebars.precompile({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }],
|
||||
})
|
||||
),
|
||||
true
|
||||
);
|
||||
expect(
|
||||
Handlebars.precompile({
|
||||
type: 'Program',
|
||||
body: [{ type: 'ContentStatement', value: 'Hello' }],
|
||||
})
|
||||
).toMatch(/return "Hello"/);
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function () {
|
||||
equal(/return ""/.test(Handlebars.precompile('')), true);
|
||||
expect(Handlebars.precompile('')).toMatch(/return ""/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// Pre-setup for browser tests. Must run before the main setup file
|
||||
// imports the Handlebars library, so that noConflict() captures this value.
|
||||
globalThis.Handlebars = 'no-conflict';
|
||||
|
||||
// Polyfill Node.js 'global' for specs that reference it at module level
|
||||
globalThis.global = globalThis;
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
import './common.js';
|
||||
import Handlebars from '../../lib/handlebars.js';
|
||||
|
||||
globalThis.Handlebars = Handlebars;
|
||||
|
||||
globalThis.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function (template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function (template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
},
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/* eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/* eslint-enable no-eval, no-console */
|
||||
}
|
||||
Vendored
+5
-13
@@ -1,22 +1,14 @@
|
||||
require('./common');
|
||||
import './common.js';
|
||||
|
||||
var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
import fs from 'fs';
|
||||
import vm from 'vm';
|
||||
import { createRequire } from 'module';
|
||||
|
||||
var chai = require('chai');
|
||||
var dirtyChai = require('dirty-chai');
|
||||
|
||||
chai.use(dirtyChai);
|
||||
global.expect = chai.expect;
|
||||
|
||||
global.sinon = require('sinon');
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
global.Handlebars = 'no-conflict';
|
||||
|
||||
var filename = 'dist/handlebars.js';
|
||||
if (global.minimizedTest) {
|
||||
filename = 'dist/handlebars.min.js';
|
||||
}
|
||||
var distHandlebars = fs.readFileSync(
|
||||
require.resolve('../../' + filename),
|
||||
'utf-8'
|
||||
|
||||
Vendored
+23
-129
@@ -1,125 +1,4 @@
|
||||
var global = (function () {
|
||||
return this;
|
||||
})();
|
||||
|
||||
var AssertError;
|
||||
if (Error.captureStackTrace) {
|
||||
AssertError = function AssertError(message, caller) {
|
||||
Error.prototype.constructor.call(this, message);
|
||||
this.message = message;
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, caller || AssertError);
|
||||
}
|
||||
};
|
||||
|
||||
AssertError.prototype = new Error();
|
||||
} else {
|
||||
AssertError = Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||
*/
|
||||
global.shouldCompileTo = function (string, hashOrArray, expected, message) {
|
||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||
*/
|
||||
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
|
||||
string,
|
||||
hashOrArray,
|
||||
partials,
|
||||
expected,
|
||||
message
|
||||
) {
|
||||
var result = compileWithPartials(string, hashOrArray, partials);
|
||||
if (result !== expected) {
|
||||
throw new AssertError(
|
||||
"'" + result + "' should === '" + expected + "': " + message,
|
||||
shouldCompileToWithPartials
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||
*/
|
||||
global.compileWithPartials = function (string, hashOrArray, partials) {
|
||||
var template, ary, options;
|
||||
if (hashOrArray && hashOrArray.hash) {
|
||||
ary = [hashOrArray.hash, hashOrArray];
|
||||
delete hashOrArray.hash;
|
||||
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||
ary = [];
|
||||
ary.push(hashOrArray[0]); // input
|
||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||
options =
|
||||
typeof hashOrArray[3] === 'object'
|
||||
? hashOrArray[3]
|
||||
: { compat: hashOrArray[3] };
|
||||
if (hashOrArray[4] != null) {
|
||||
options.data = !!hashOrArray[4];
|
||||
ary[1].data = hashOrArray[4];
|
||||
}
|
||||
} else {
|
||||
ary = [hashOrArray];
|
||||
}
|
||||
|
||||
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
|
||||
string,
|
||||
options
|
||||
);
|
||||
return template.apply(this, ary);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||
* @see https://www.chaijs.com/api/bdd/
|
||||
*/
|
||||
global.equals = global.equal = function equals(a, b, msg) {
|
||||
if (a !== b) {
|
||||
throw new AssertError(
|
||||
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
|
||||
equals
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||
* @see https://www.chaijs.com/api/bdd/#method_throw
|
||||
*/
|
||||
global.shouldThrow = function (callback, type, msg) {
|
||||
var failed;
|
||||
try {
|
||||
callback();
|
||||
failed = true;
|
||||
} catch (caught) {
|
||||
if (type && !(caught instanceof type)) {
|
||||
throw new AssertError('Type failure: ' + caught);
|
||||
}
|
||||
if (
|
||||
msg &&
|
||||
!(msg.test ? msg.test(caught.message) : msg === caught.message)
|
||||
) {
|
||||
throw new AssertError(
|
||||
'Throw mismatch: Expected ' +
|
||||
caught.message +
|
||||
' to match ' +
|
||||
msg +
|
||||
'\n\n' +
|
||||
caught.stack,
|
||||
shouldThrow
|
||||
);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
throw new AssertError('It failed to throw', shouldThrow);
|
||||
}
|
||||
};
|
||||
var global = globalThis;
|
||||
|
||||
global.expectTemplate = function (templateAsString) {
|
||||
return new HandlebarsTestBench(templateAsString);
|
||||
@@ -200,18 +79,29 @@ HandlebarsTestBench.prototype.withMessage = function (message) {
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype.toCompileTo = function (expectedOutputAsString) {
|
||||
expect(this._compileAndExecute()).to.equal(
|
||||
expectedOutputAsString,
|
||||
this.message
|
||||
);
|
||||
expect(this._compileAndExecute()).toBe(expectedOutputAsString);
|
||||
};
|
||||
|
||||
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||
HandlebarsTestBench.prototype.toThrow = function (errorLike, errMsgMatcher) {
|
||||
var self = this;
|
||||
expect(function () {
|
||||
var caught;
|
||||
try {
|
||||
self._compileAndExecute();
|
||||
}).to.throw(errorLike, errMsgMatcher, this.message);
|
||||
} catch (e) {
|
||||
caught = e;
|
||||
}
|
||||
|
||||
expect(caught).toBeDefined();
|
||||
|
||||
if (typeof errorLike === 'function') {
|
||||
expect(caught).toBeInstanceOf(errorLike);
|
||||
if (errMsgMatcher) {
|
||||
expect(caught.message).toMatch(errMsgMatcher);
|
||||
}
|
||||
} else if (errorLike) {
|
||||
// errorLike is a string or regex message matcher (single-argument form)
|
||||
expect(caught.message).toMatch(errorLike);
|
||||
}
|
||||
};
|
||||
|
||||
HandlebarsTestBench.prototype._compileAndExecute = function () {
|
||||
@@ -237,3 +127,7 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function () {
|
||||
combinedRuntimeOptions.decorators = this.decorators;
|
||||
return combinedRuntimeOptions;
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
global.handlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
Vendored
+3
-10
@@ -1,14 +1,7 @@
|
||||
require('./common');
|
||||
import './common.js';
|
||||
import Handlebars from '../../lib/index.js';
|
||||
|
||||
var chai = require('chai');
|
||||
var dirtyChai = require('dirty-chai');
|
||||
|
||||
chai.use(dirtyChai);
|
||||
global.expect = chai.expect;
|
||||
|
||||
global.sinon = require('sinon');
|
||||
|
||||
global.Handlebars = require('../../lib');
|
||||
global.Handlebars = Handlebars;
|
||||
|
||||
global.CompilerContext = {
|
||||
compile: function (template, options) {
|
||||
|
||||
Vendored
-63
@@ -1,63 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
var fs = require('fs'),
|
||||
Mocha = require('mocha'),
|
||||
path = require('path');
|
||||
|
||||
var errors = 0,
|
||||
testDir = path.dirname(__dirname),
|
||||
grep = process.argv[2];
|
||||
|
||||
// Lazy hack, but whatever
|
||||
if (grep === '--min') {
|
||||
global.minimizedTest = true;
|
||||
grep = undefined;
|
||||
}
|
||||
|
||||
var files = fs
|
||||
.readdirSync(testDir)
|
||||
.filter(function (name) {
|
||||
return /.*\.js$/.test(name);
|
||||
})
|
||||
.map(function (name) {
|
||||
return testDir + path.sep + name;
|
||||
});
|
||||
|
||||
if (global.minimizedTest) {
|
||||
run('./runtime', function () {
|
||||
run('./browser', function () {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/* eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
} else {
|
||||
run('./runtime', function () {
|
||||
run('./browser', function () {
|
||||
run('./node', function () {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/* eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function run(env, callback) {
|
||||
var mocha = new Mocha();
|
||||
mocha.ui('bdd');
|
||||
mocha.files = files.slice();
|
||||
if (grep) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
files.forEach(function (name) {
|
||||
delete require.cache[name];
|
||||
});
|
||||
|
||||
console.log('Running env: ' + env);
|
||||
require(env);
|
||||
mocha.run(function (errorCount) {
|
||||
errors += errorCount;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
Vendored
-73
@@ -1,73 +0,0 @@
|
||||
require('./common');
|
||||
|
||||
var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
var chai = require('chai');
|
||||
var dirtyChai = require('dirty-chai');
|
||||
|
||||
chai.use(dirtyChai);
|
||||
global.expect = chai.expect;
|
||||
|
||||
global.sinon = require('sinon');
|
||||
|
||||
global.Handlebars = 'no-conflict';
|
||||
|
||||
var filename = 'dist/handlebars.runtime.js';
|
||||
if (global.minimizedTest) {
|
||||
filename = 'dist/handlebars.runtime.min.js';
|
||||
}
|
||||
vm.runInThisContext(
|
||||
fs.readFileSync(__dirname + '/../../' + filename),
|
||||
filename
|
||||
);
|
||||
|
||||
var parse = require('@handlebars/parser').parse;
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function (template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.precompile = function (
|
||||
precompileTemplate,
|
||||
precompileOptions
|
||||
) {
|
||||
return compiler.precompile(
|
||||
precompileTemplate,
|
||||
precompileOptions,
|
||||
handlebarsEnv
|
||||
);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
|
||||
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function (template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.compile = function (compileTemplate, compileOptions) {
|
||||
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
|
||||
|
||||
return handlebarsEnv.compile(template, options);
|
||||
},
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/* eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/* eslint-enable no-eval, no-console */
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "a";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "";
|
||||
},"useData":true});
|
||||
});
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
define(["handlebars.runtime"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).empty=t({compiler:[8,">= 4.3.0"],main:function(e,t,a,n,r){return""},useData:!0})});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
{"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true}
|
||||
@@ -1,10 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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) {
|
||||
return "<div>1</div>";
|
||||
},"useData":true});
|
||||
templates['secondTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "<div>2</div>";
|
||||
},"useData":true});
|
||||
return templates;
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "<div>Test Partial</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['some-path/handlebars.runtime'], function(Handlebars) {
|
||||
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 "";
|
||||
},"useData":true});
|
||||
});
|
||||
+18
-23
@@ -2,26 +2,21 @@ Precompile handlebar templates.
|
||||
Usage: handlebars.js [template|directory]...
|
||||
|
||||
Options:
|
||||
--help Outputs this message [boolean]
|
||||
-f, --output Output File [string]
|
||||
--map Source Map File [string]
|
||||
--esm Exports ECMAScript module style, path to Handlebars module (eg. "handlebars/lib/handlebars")
|
||||
[string] [default: null]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string] [default: null]
|
||||
-a, --amd Exports AMD style (require.js) [deprecated] [boolean]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for AMD-style) [deprecated] [string] [default: ""]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-n, --namespace Template namespace [string] [default: "Handlebars.templates"]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-N, --name Name of passed string templates. Optional if running in a simple mode. Required when operating on
|
||||
multiple templates. [string]
|
||||
-i, --string Generates a template from the passed CLI argument.
|
||||
"-" is treated as a special value and causes stdin to be read for the template value. [string]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string] [default: "handlebars"]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
-v, --version Show version number [boolean]
|
||||
-f, --output Output File [string]
|
||||
--map Source Map File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-n, --namespace Template namespace [string] [default: "Handlebars.templates"]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-N, --name Name of passed string templates. Optional if running in a simple mode. Required when operating on
|
||||
multiple templates. [string]
|
||||
-i, --string Generates a template from the passed CLI argument.
|
||||
"-" is treated as a special value and causes stdin to be read for the template value. [string]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string] [default: "handlebars"]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
-v, --version Prints the current compiler version [boolean]
|
||||
--help Outputs this message [boolean]
|
||||
@@ -1,10 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
return templates;
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "<div>This is a test</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['known.helpers'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
return undefined
|
||||
};
|
||||
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 : "");
|
||||
},"2":function(container,depth0,helpers,partials,data) {
|
||||
return " <div>Another known helper</div>\n";
|
||||
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
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 : "");
|
||||
},"useData":true});
|
||||
});
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
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 "<div>Test Partial</div>";
|
||||
},"useData":true});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
define(["handlebars.runtime"],function(e){var t=(e=e.default).template;return(e.templates=e.templates||{}).test=t({compiler:[8,">= 4.3.0"],main:function(e,t,a,n,i){return"<div>1</div>"},useData:!0})});
|
||||
//# sourceMappingURL=./spec/tmp/source.map.amd.txt
|
||||
+18
-22
@@ -53,7 +53,7 @@ describe('helpers', function () {
|
||||
runWithIdentityHelper('{{{{identity}}}}{{{{/identity}}}}', '');
|
||||
});
|
||||
|
||||
xit('helper for nested raw block works if nested raw blocks are broken', function () {
|
||||
it.skip('helper for nested raw block works if nested raw blocks are broken', function () {
|
||||
// This test was introduced in 4.4.4, but it was not the actual problem that lead to the patch release
|
||||
// The test is deactivated, because in 3.x this template cases an exception and it also does not work in 4.4.3
|
||||
// If anyone can make this template work without breaking everything else, then go for it,
|
||||
@@ -392,7 +392,7 @@ describe('helpers', function () {
|
||||
return 'fail';
|
||||
});
|
||||
handlebarsEnv.unregisterHelper('foo');
|
||||
equals(handlebarsEnv.helpers.foo, undefined);
|
||||
expect(handlebarsEnv.helpers.foo).toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows multiple globals', function () {
|
||||
@@ -417,23 +417,19 @@ describe('helpers', function () {
|
||||
});
|
||||
|
||||
it('fails with multiple and args', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
handlebarsEnv.registerHelper(
|
||||
{
|
||||
world: function () {
|
||||
return 'world!';
|
||||
},
|
||||
testHelper: function () {
|
||||
return 'found it!';
|
||||
},
|
||||
expect(function () {
|
||||
handlebarsEnv.registerHelper(
|
||||
{
|
||||
world: function () {
|
||||
return 'world!';
|
||||
},
|
||||
{}
|
||||
);
|
||||
},
|
||||
Error,
|
||||
'Arg not supported with multiple helpers'
|
||||
);
|
||||
testHelper: function () {
|
||||
return 'found it!';
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
}).toThrow('Arg not supported with multiple helpers');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -920,7 +916,7 @@ describe('helpers', function () {
|
||||
expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}')
|
||||
.withInput({ value: 'foo' })
|
||||
.withHelper('goodbyes', function (options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
expect(options.fn.blockParams).toBe(1);
|
||||
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
|
||||
})
|
||||
.toCompileTo('1foo');
|
||||
@@ -932,7 +928,7 @@ describe('helpers', function () {
|
||||
return 'foo';
|
||||
})
|
||||
.withHelper('goodbyes', function (options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
expect(options.fn.blockParams).toBe(1);
|
||||
return options.fn({}, { blockParams: [1, 2] });
|
||||
})
|
||||
.toCompileTo('1foo');
|
||||
@@ -947,7 +943,7 @@ describe('helpers', function () {
|
||||
return 'foo';
|
||||
})
|
||||
.withHelper('goodbyes', function (options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
expect(options.fn.blockParams).toBe(1);
|
||||
return options.fn(this, { blockParams: [1, 2] });
|
||||
})
|
||||
.toCompileTo('barfoo');
|
||||
@@ -977,7 +973,7 @@ describe('helpers', function () {
|
||||
)
|
||||
.withInput({ value: 'foo' })
|
||||
.withHelper('goodbyes', function (options) {
|
||||
equals(options.fn.blockParams, 1);
|
||||
expect(options.fn.blockParams).toBe(1);
|
||||
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
|
||||
})
|
||||
.toCompileTo('1foo');
|
||||
|
||||
+48
-91
@@ -1,98 +1,55 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/dist/handlebars.js"></script>
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/tmp/tests.js"></script>
|
||||
<script>
|
||||
onload = function(){
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
// Reporting to test-runner
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<title>Handlebars UMD Smoke Test</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<h1>Handlebars UMD Smoke Test</h1>
|
||||
<pre id="results"></pre>
|
||||
<script src="/dist/handlebars.js"></script>
|
||||
<script>
|
||||
var results = document.getElementById('results');
|
||||
var failures = 0;
|
||||
var tests = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
tests++;
|
||||
if (!condition) {
|
||||
failures++;
|
||||
results.textContent += 'FAIL: ' + message + '\n';
|
||||
} else {
|
||||
results.textContent += 'PASS: ' + message + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
assert(typeof Handlebars !== 'undefined', 'Handlebars is defined');
|
||||
assert(
|
||||
typeof Handlebars.compile === 'function',
|
||||
'Handlebars.compile exists'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.template === 'function',
|
||||
'Handlebars.template exists'
|
||||
);
|
||||
assert(
|
||||
typeof Handlebars.VERSION === 'string',
|
||||
'Handlebars.VERSION exists'
|
||||
);
|
||||
|
||||
var template = Handlebars.compile('Hello {{name}}!');
|
||||
var output = template({ name: 'World' });
|
||||
assert(output === 'Hello World!', 'Basic compilation works: ' + output);
|
||||
} catch (e) {
|
||||
failures++;
|
||||
results.textContent += 'ERROR: ' + e.message + '\n';
|
||||
}
|
||||
|
||||
results.textContent +=
|
||||
'\n' + tests + ' tests, ' + failures + ' failures\n';
|
||||
window.mochaResults = { passes: tests - failures, failures: failures };
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+31
-28
@@ -19,11 +19,9 @@ describe('javascript-compiler api', function () {
|
||||
) {
|
||||
return parent + '.bar_' + name;
|
||||
};
|
||||
/* eslint-disable camelcase */
|
||||
expectTemplate('{{foo}}')
|
||||
.withInput({ bar_foo: 'food' })
|
||||
.toCompileTo('food');
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
||||
@@ -34,30 +32,35 @@ describe('javascript-compiler api', function () {
|
||||
.toCompileTo('food');
|
||||
});
|
||||
});
|
||||
describe('#compilerInfo', function () {
|
||||
var $superCheck, $superInfo;
|
||||
beforeEach(function () {
|
||||
$superCheck = handlebarsEnv.VM.checkRevision;
|
||||
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
|
||||
});
|
||||
afterEach(function () {
|
||||
handlebarsEnv.VM.checkRevision = $superCheck;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
|
||||
});
|
||||
it('should allow compilerInfo override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function () {
|
||||
return 'crazy';
|
||||
};
|
||||
handlebarsEnv.VM.checkRevision = function (compilerInfo) {
|
||||
if (compilerInfo !== 'crazy') {
|
||||
throw new Error("It didn't work");
|
||||
}
|
||||
};
|
||||
expectTemplate('{{foo}} ')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('food ');
|
||||
});
|
||||
});
|
||||
// Monkey-patching VM.checkRevision is not possible when VM is an ESM
|
||||
// namespace object (browser mode), so skip these tests in that context.
|
||||
(CompilerContext.browser ? describe.skip : describe)(
|
||||
'#compilerInfo',
|
||||
function () {
|
||||
var $superCheck, $superInfo;
|
||||
beforeEach(function () {
|
||||
$superCheck = handlebarsEnv.VM.checkRevision;
|
||||
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
|
||||
});
|
||||
afterEach(function () {
|
||||
handlebarsEnv.VM.checkRevision = $superCheck;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
|
||||
});
|
||||
it('should allow compilerInfo override', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function () {
|
||||
return 'crazy';
|
||||
};
|
||||
handlebarsEnv.VM.checkRevision = function (compilerInfo) {
|
||||
if (compilerInfo !== 'crazy') {
|
||||
throw new Error("It didn't work");
|
||||
}
|
||||
};
|
||||
expectTemplate('{{foo}} ')
|
||||
.withInput({ foo: 'food' })
|
||||
.toCompileTo('food ');
|
||||
});
|
||||
}
|
||||
);
|
||||
describe('buffer', function () {
|
||||
var $superAppend, $superCreate;
|
||||
beforeEach(function () {
|
||||
@@ -105,7 +108,7 @@ describe('javascript-compiler api', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
|
||||
validVariableName
|
||||
)
|
||||
).to.be.true();
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
[('123test', 'abc()', 'abc.cde')].forEach(function (invalidVariableName) {
|
||||
@@ -114,7 +117,7 @@ describe('javascript-compiler api', function () {
|
||||
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
|
||||
invalidVariableName
|
||||
)
|
||||
).to.be.false();
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+5
-8
@@ -164,12 +164,9 @@ describe('partials', function () {
|
||||
});
|
||||
|
||||
it('registering undefined partial throws an exception', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
var undef;
|
||||
handlebarsEnv.registerPartial('undefined_test', undef);
|
||||
},
|
||||
Handlebars.Exception,
|
||||
expect(function () {
|
||||
handlebarsEnv.registerPartial('undefined_test', undefined);
|
||||
}).toThrow(
|
||||
'Attempting to register a partial called "undefined_test" as undefined'
|
||||
);
|
||||
});
|
||||
@@ -231,7 +228,7 @@ describe('partials', function () {
|
||||
.toCompileTo('Dudes: Jeepers Creepers');
|
||||
|
||||
handlebarsEnv.unregisterPartial('globalTest');
|
||||
equals(handlebarsEnv.partials.globalTest, undefined);
|
||||
expect(handlebarsEnv.partials.globalTest).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Multiple partial registration', function () {
|
||||
@@ -556,7 +553,7 @@ describe('partials', function () {
|
||||
var env = Handlebars.create();
|
||||
env.registerPartial('partial', '{{foo}}');
|
||||
var template = env.compile('{{foo}} {{> partial}}', { noEscape: true });
|
||||
equal(template({ foo: '<' }), '< <');
|
||||
expect(template({ foo: '<' })).toBe('< <');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+170
-280
@@ -1,15 +1,19 @@
|
||||
/* eslint-disable no-console */
|
||||
import Handlebars from '../lib/index.js';
|
||||
import * as Precompiler from '../lib/precompiler.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
describe('precompiler', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var Handlebars = require('../lib'),
|
||||
Precompiler = require('../dist/cjs/precompiler'),
|
||||
fs = require('fs'),
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var log,
|
||||
logFunction,
|
||||
errorLog,
|
||||
@@ -25,34 +29,6 @@ describe('precompiler', function () {
|
||||
content,
|
||||
writeFileSync;
|
||||
|
||||
/**
|
||||
* Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded.
|
||||
*
|
||||
* The function cleans up its mess when "callback" is finished
|
||||
*
|
||||
* @param {Error} loadError the error that should be thrown if uglify is loaded
|
||||
* @param {function} callback a callback-function to run when the mock is active.
|
||||
*/
|
||||
function mockRequireUglify(loadError, callback) {
|
||||
var Module = require('module');
|
||||
var _resolveFilename = Module._resolveFilename;
|
||||
delete require.cache[require.resolve('uglify-js')];
|
||||
delete require.cache[require.resolve('../dist/cjs/precompiler')];
|
||||
Module._resolveFilename = function (request, mod) {
|
||||
if (request === 'uglify-js') {
|
||||
throw loadError;
|
||||
}
|
||||
return _resolveFilename.call(this, request, mod);
|
||||
};
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
Module._resolveFilename = _resolveFilename;
|
||||
delete require.cache[require.resolve('uglify-js')];
|
||||
delete require.cache[require.resolve('../dist/cjs/precompiler')];
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
precompile = Handlebars.precompile;
|
||||
minify = uglify.minify;
|
||||
@@ -83,333 +59,247 @@ describe('precompiler', function () {
|
||||
console.error = errorLogFunction;
|
||||
});
|
||||
|
||||
it('should output version', function () {
|
||||
Precompiler.cli({ templates: [], version: true });
|
||||
equals(log, Handlebars.VERSION);
|
||||
it('should output version', async function () {
|
||||
await Precompiler.cli({ templates: [], version: true });
|
||||
expect(log).toBe(Handlebars.VERSION);
|
||||
});
|
||||
it('should throw if lacking templates', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Precompiler.cli({ templates: [] });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
it('should throw if lacking templates', async function () {
|
||||
await expect(Precompiler.cli({ templates: [] })).rejects.toThrow(
|
||||
'Must define at least one template or directory.'
|
||||
);
|
||||
});
|
||||
it('should handle empty/filtered directories', function () {
|
||||
Precompiler.cli({ hasDirectory: true, templates: [] });
|
||||
it('should handle empty/filtered directories', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
await Precompiler.cli({ hasDirectory: true, templates: [] });
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should throw when combining simple and minimized', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Precompiler.cli({ templates: [__dirname], simple: true, min: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Unable to minimize simple output'
|
||||
);
|
||||
it('should throw when combining simple and minimized', async function () {
|
||||
await expect(
|
||||
Precompiler.cli({ templates: [__dirname], simple: true, min: true })
|
||||
).rejects.toThrow('Unable to minimize simple output');
|
||||
});
|
||||
it('should throw when combining simple and multiple templates', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Precompiler.cli({
|
||||
templates: [
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
],
|
||||
simple: true,
|
||||
});
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Unable to output multiple templates in simple mode'
|
||||
);
|
||||
it('should throw when combining simple and multiple templates', async function () {
|
||||
await expect(
|
||||
Precompiler.cli({
|
||||
templates: [
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
__dirname + '/artifacts/empty.handlebars',
|
||||
],
|
||||
simple: true,
|
||||
})
|
||||
).rejects.toThrow('Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should throw when missing name', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Precompiler.cli({ templates: [{ source: '' }], amd: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Name missing for template'
|
||||
);
|
||||
it('should throw when missing name', async function () {
|
||||
await expect(
|
||||
Precompiler.cli({
|
||||
templates: [{ source: '' }, { source: '' }],
|
||||
hasDirectory: true,
|
||||
})
|
||||
).rejects.toThrow('Name missing for template');
|
||||
});
|
||||
it('should throw when combining simple and directories', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Unable to output multiple templates in simple mode'
|
||||
);
|
||||
it('should throw when combining simple and directories', async function () {
|
||||
await expect(
|
||||
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true })
|
||||
).rejects.toThrow('Unable to output multiple templates in simple mode');
|
||||
});
|
||||
|
||||
it('should output simple templates', function () {
|
||||
it('should output simple templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], simple: true });
|
||||
equal(log, 'simple\n');
|
||||
await Precompiler.cli({ templates: [emptyTemplate], simple: true });
|
||||
expect(log).toBe('simple\n');
|
||||
});
|
||||
it('should default to simple templates', function () {
|
||||
it('should default to simple templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [{ source: '' }] });
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output amd templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], amd: true });
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
namespace: 'foo',
|
||||
});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output amd partials', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd partials', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({
|
||||
templates: [emptyTemplate, emptyTemplate],
|
||||
amd: true,
|
||||
partial: true,
|
||||
});
|
||||
equal(/return Handlebars\.partials\[/.test(log), false);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output es module templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'esm';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], esm: true });
|
||||
equal(/template\(esm\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'commonjs';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
|
||||
equal(/template\(commonjs\)/.test(log), true);
|
||||
await Precompiler.cli({ templates: [{ source: '' }] });
|
||||
expect(log).toBe('simple\n');
|
||||
});
|
||||
|
||||
it('should set data flag', function () {
|
||||
it('should output wrapped templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'wrapped';
|
||||
};
|
||||
await Precompiler.cli({ templates: [emptyTemplate] });
|
||||
expect(log).toMatch(/template\(wrapped\)/);
|
||||
expect(log).toMatch(/\(function\(\)/);
|
||||
});
|
||||
|
||||
it('should set data flag', async function () {
|
||||
Handlebars.precompile = function (data, options) {
|
||||
equal(options.data, true);
|
||||
expect(options.data).toBe(true);
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], simple: true, data: true });
|
||||
equal(log, 'simple\n');
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
simple: true,
|
||||
data: true,
|
||||
});
|
||||
expect(log).toBe('simple\n');
|
||||
});
|
||||
|
||||
it('should set known helpers', function () {
|
||||
it('should set known helpers', async function () {
|
||||
Handlebars.precompile = function (data, options) {
|
||||
equal(options.knownHelpers.foo, true);
|
||||
expect(options.knownHelpers.foo).toBe(true);
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
|
||||
equal(log, 'simple\n');
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
simple: true,
|
||||
known: 'foo',
|
||||
});
|
||||
expect(log).toBe('simple\n');
|
||||
});
|
||||
it('should output to file system', function () {
|
||||
it('should output to file system', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'simple';
|
||||
};
|
||||
Precompiler.cli({
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
simple: true,
|
||||
output: 'file!',
|
||||
});
|
||||
equal(file, 'file!');
|
||||
equal(content, 'simple\n');
|
||||
equal(log, '');
|
||||
expect(file).toBe('file!');
|
||||
expect(content).toBe('simple\n');
|
||||
expect(log).toBe('');
|
||||
});
|
||||
|
||||
it('should output minimized templates', function () {
|
||||
it('should output minimized templates', async function () {
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
return 'iife';
|
||||
};
|
||||
uglify.minify = function () {
|
||||
return { code: 'min' };
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
equal(log, 'min');
|
||||
await Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
expect(log).toBe('min');
|
||||
});
|
||||
|
||||
it('should omit minimization gracefully, if uglify-js is missing', function () {
|
||||
var error = new Error("Cannot find module 'uglify-js'");
|
||||
error.code = 'MODULE_NOT_FOUND';
|
||||
mockRequireUglify(error, function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
equal(/\n/.test(log), true);
|
||||
equal(/Code minimization is disabled/.test(errorLog), true);
|
||||
});
|
||||
it('should output map', async function () {
|
||||
await Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
|
||||
|
||||
expect(file).toBe('foo.js.map');
|
||||
expect(log.match(/sourceMappingURL=/g).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should fail on errors (other than missing module) while loading uglify-js', function () {
|
||||
mockRequireUglify(new Error('Mock Error'), function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Handlebars.precompile = function () {
|
||||
return 'amd';
|
||||
};
|
||||
Precompiler.cli({ templates: [emptyTemplate], min: true });
|
||||
},
|
||||
Error,
|
||||
'Mock Error'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should output map', function () {
|
||||
Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
});
|
||||
|
||||
it('should output map', function () {
|
||||
Precompiler.cli({
|
||||
it('should output map with minification', async function () {
|
||||
await Precompiler.cli({
|
||||
templates: [emptyTemplate],
|
||||
min: true,
|
||||
map: 'foo.js.map',
|
||||
});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
expect(file).toBe('foo.js.map');
|
||||
expect(log.match(/sourceMappingURL=/g).length).toBe(1);
|
||||
});
|
||||
|
||||
describe('#loadTemplates', function () {
|
||||
it('should throw on missing template', function (done) {
|
||||
Precompiler.loadTemplates({ files: ['foo'] }, function (err) {
|
||||
equal(err.message, 'Unable to open template file "foo"');
|
||||
done();
|
||||
function loadTemplatesAsync(inputOpts) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
Precompiler.loadTemplates(inputOpts, function (err, opts) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(opts);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should enumerate directories by extension', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
|
||||
function (err, opts) {
|
||||
equal(opts.templates.length, 2);
|
||||
equal(opts.templates[0].name, 'example_2');
|
||||
}
|
||||
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
it('should throw on missing template', async function () {
|
||||
try {
|
||||
await loadTemplatesAsync({ files: ['foo'] });
|
||||
throw new Error('should have thrown');
|
||||
} catch (err) {
|
||||
expect(err.message).toBe('Unable to open template file "foo"');
|
||||
}
|
||||
});
|
||||
it('should enumerate all templates by extension', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
|
||||
function (err, opts) {
|
||||
equal(opts.templates.length, 5);
|
||||
equal(opts.templates[0].name, 'bom');
|
||||
equal(opts.templates[1].name, 'empty');
|
||||
equal(opts.templates[2].name, 'example_1');
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
it('should enumerate directories by extension', async function () {
|
||||
var opts = await loadTemplatesAsync({
|
||||
files: [__dirname + '/artifacts'],
|
||||
extension: 'hbs',
|
||||
});
|
||||
expect(opts.templates.length).toBe(2);
|
||||
expect(opts.templates[0].name).toBe('example_2');
|
||||
});
|
||||
it('should handle regular expression characters in extensions', function (done) {
|
||||
Precompiler.loadTemplates(
|
||||
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
|
||||
function (err) {
|
||||
// Success is not throwing
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
it('should enumerate all templates by extension', async function () {
|
||||
var opts = await loadTemplatesAsync({
|
||||
files: [__dirname + '/artifacts'],
|
||||
extension: 'handlebars',
|
||||
});
|
||||
expect(opts.templates.length).toBe(5);
|
||||
expect(opts.templates[0].name).toBe('bom');
|
||||
expect(opts.templates[1].name).toBe('empty');
|
||||
expect(opts.templates[2].name).toBe('example_1');
|
||||
});
|
||||
it('should handle BOM', function (done) {
|
||||
var opts = {
|
||||
it('should handle regular expression characters in extensions', async function () {
|
||||
await loadTemplatesAsync({
|
||||
files: [__dirname + '/artifacts'],
|
||||
extension: 'hb(s',
|
||||
});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should handle BOM', async function () {
|
||||
var opts = await loadTemplatesAsync({
|
||||
files: [__dirname + '/artifacts/bom.handlebars'],
|
||||
extension: 'handlebars',
|
||||
bom: true,
|
||||
};
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].source, 'a');
|
||||
done(err);
|
||||
});
|
||||
expect(opts.templates[0].source).toBe('a');
|
||||
});
|
||||
|
||||
it('should handle different root', function (done) {
|
||||
var opts = {
|
||||
it('should handle different root', async function () {
|
||||
var opts = await loadTemplatesAsync({
|
||||
files: [__dirname + '/artifacts/empty.handlebars'],
|
||||
simple: true,
|
||||
root: 'foo/',
|
||||
};
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
|
||||
done(err);
|
||||
});
|
||||
expect(opts.templates[0].name).toBe(__dirname + '/artifacts/empty');
|
||||
});
|
||||
|
||||
it('should accept string inputs', function (done) {
|
||||
var opts = { string: '' };
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, undefined);
|
||||
equal(opts.templates[0].source, '');
|
||||
done(err);
|
||||
});
|
||||
it('should accept string inputs', async function () {
|
||||
var opts = await loadTemplatesAsync({ string: '' });
|
||||
expect(opts.templates[0].name).toBeUndefined();
|
||||
expect(opts.templates[0].source).toBe('');
|
||||
});
|
||||
it('should accept string array inputs', function (done) {
|
||||
var opts = { string: ['', 'bar'], name: ['beep', 'boop'] };
|
||||
Precompiler.loadTemplates(opts, function (err, opts) {
|
||||
equal(opts.templates[0].name, 'beep');
|
||||
equal(opts.templates[0].source, '');
|
||||
equal(opts.templates[1].name, 'boop');
|
||||
equal(opts.templates[1].source, 'bar');
|
||||
done(err);
|
||||
it('should accept string array inputs', async function () {
|
||||
var opts = await loadTemplatesAsync({
|
||||
string: ['', 'bar'],
|
||||
name: ['beep', 'boop'],
|
||||
});
|
||||
expect(opts.templates[0].name).toBe('beep');
|
||||
expect(opts.templates[0].source).toBe('');
|
||||
expect(opts.templates[1].name).toBe('boop');
|
||||
expect(opts.templates[1].source).toBe('bar');
|
||||
});
|
||||
it('should accept stdin input', function (done) {
|
||||
var stdin = require('mock-stdin').stdin();
|
||||
Precompiler.loadTemplates({ string: '-' }, function (err, opts) {
|
||||
equal(opts.templates[0].source, 'foo');
|
||||
done(err);
|
||||
});
|
||||
stdin.send('fo');
|
||||
stdin.send('o');
|
||||
stdin.end();
|
||||
it('should accept stdin input', async function () {
|
||||
var { stdin } = await import('mock-stdin');
|
||||
var stdinMock = stdin();
|
||||
var promise = loadTemplatesAsync({ string: '-' });
|
||||
stdinMock.send('fo');
|
||||
stdinMock.send('o');
|
||||
stdinMock.end();
|
||||
var opts = await promise;
|
||||
expect(opts.templates[0].source).toBe('foo');
|
||||
});
|
||||
it('error on name missing', function (done) {
|
||||
var opts = { string: ['', 'bar'] };
|
||||
Precompiler.loadTemplates(opts, function (err) {
|
||||
equal(
|
||||
err.message,
|
||||
it('error on name missing', async function () {
|
||||
try {
|
||||
await loadTemplatesAsync({ string: ['', 'bar'] });
|
||||
throw new Error('should have thrown');
|
||||
} catch (err) {
|
||||
expect(err.message).toBe(
|
||||
'Number of names did not match the number of string inputs'
|
||||
);
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should complete when no args are passed', function (done) {
|
||||
Precompiler.loadTemplates({}, function (err, opts) {
|
||||
equal(opts.templates.length, 0);
|
||||
done(err);
|
||||
});
|
||||
it('should complete when no args are passed', async function () {
|
||||
var opts = await loadTemplatesAsync({});
|
||||
expect(opts.templates.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+14
-15
@@ -337,7 +337,10 @@ describe('Regressions', function () {
|
||||
},
|
||||
};
|
||||
|
||||
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
|
||||
expectTemplate('{{helpa length="foo"}}')
|
||||
.withInput(obj)
|
||||
.withHelpers(helpers)
|
||||
.toCompileTo('foo');
|
||||
});
|
||||
|
||||
it('GH-1319: "unless" breaks when "each" value equals "null"', function () {
|
||||
@@ -373,20 +376,16 @@ describe('Regressions', function () {
|
||||
var result = newHandlebarsInstance.templates['test.hbs']({
|
||||
name: 'yehuda',
|
||||
});
|
||||
equals(result.trim(), 'YEHUDA');
|
||||
expect(result.trim()).toBe('YEHUDA');
|
||||
});
|
||||
|
||||
it('should call "helperMissing" if a helper is missing', function () {
|
||||
var newHandlebarsInstance = Handlebars.create();
|
||||
|
||||
shouldThrow(
|
||||
function () {
|
||||
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
|
||||
newHandlebarsInstance.templates['test.hbs']({});
|
||||
},
|
||||
Handlebars.Exception,
|
||||
'Missing helper: "loud"'
|
||||
);
|
||||
expect(function () {
|
||||
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
|
||||
newHandlebarsInstance.templates['test.hbs']({});
|
||||
}).toThrow('Missing helper: "loud"');
|
||||
});
|
||||
|
||||
it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function () {
|
||||
@@ -403,7 +402,7 @@ describe('Regressions', function () {
|
||||
property: 'a',
|
||||
test: { a: 'b' },
|
||||
})
|
||||
).to.equal('b');
|
||||
).toBe('b');
|
||||
});
|
||||
|
||||
function registerTemplate(Handlebars, compileTemplate) {
|
||||
@@ -479,19 +478,19 @@ describe('Regressions', function () {
|
||||
newHandlebarsInstance = Handlebars.create();
|
||||
});
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should only compile global partials once', function () {
|
||||
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
|
||||
var templateSpy = vi.spyOn(newHandlebarsInstance, 'template');
|
||||
newHandlebarsInstance.registerPartial({
|
||||
dude: 'I am a partial',
|
||||
});
|
||||
var string = 'Dudes: {{> dude}} {{> dude}}';
|
||||
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
|
||||
newHandlebarsInstance.compile(string)(); // This should only compile template
|
||||
equal(templateSpy.callCount, 3);
|
||||
sinon.restore();
|
||||
expect(templateSpy).toHaveBeenCalledTimes(3);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
|
||||
describe('Require', function () {
|
||||
it('Load .handlebars files with require()', function () {
|
||||
var template = require('./artifacts/example_1');
|
||||
equal(template, require('./artifacts/example_1.handlebars'));
|
||||
|
||||
var expected = 'foo\n';
|
||||
var result = template({ foo: 'foo' });
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function () {
|
||||
var template = require('./artifacts/example_2');
|
||||
equal(template, require('./artifacts/example_2.hbs'));
|
||||
|
||||
var expected = 'Hello, World!\n';
|
||||
var result = template({ name: 'World' });
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
+31
-50
@@ -1,74 +1,55 @@
|
||||
describe('runtime', function () {
|
||||
describe('#template', function () {
|
||||
it('should throw on invalid templates', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template({});
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: object'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template();
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: undefined'
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template('');
|
||||
},
|
||||
Error,
|
||||
'Unknown template object: string'
|
||||
);
|
||||
expect(function () {
|
||||
Handlebars.template({});
|
||||
}).toThrow('Unknown template object: object');
|
||||
expect(function () {
|
||||
Handlebars.template();
|
||||
}).toThrow('Unknown template object: undefined');
|
||||
expect(function () {
|
||||
Handlebars.template('');
|
||||
}).toThrow('Unknown template object: string');
|
||||
});
|
||||
it('should throw on version mismatch', function () {
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1],
|
||||
});
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1],
|
||||
});
|
||||
}).toThrow(
|
||||
/Template was precompiled with a newer version of Handlebars than the current runtime/
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1],
|
||||
});
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1],
|
||||
});
|
||||
}).toThrow(
|
||||
/Template was precompiled with an older version of Handlebars than the current runtime/
|
||||
);
|
||||
shouldThrow(
|
||||
function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
});
|
||||
},
|
||||
Error,
|
||||
expect(function () {
|
||||
Handlebars.template({
|
||||
main: {},
|
||||
});
|
||||
}).toThrow(
|
||||
/Template was precompiled with an older version of Handlebars than the current runtime/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#noConflict', function () {
|
||||
if (!CompilerContext.browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should reset on no conflict', function () {
|
||||
if (!CompilerContext.browser) {
|
||||
return;
|
||||
}
|
||||
var reset = Handlebars;
|
||||
Handlebars.noConflict();
|
||||
equal(Handlebars, 'no-conflict');
|
||||
expect(Handlebars).toBe('no-conflict');
|
||||
|
||||
Handlebars = 'really, none';
|
||||
reset.noConflict();
|
||||
equal(Handlebars, 'really, none');
|
||||
expect(Handlebars).toBe('really, none');
|
||||
|
||||
Handlebars = reset;
|
||||
});
|
||||
|
||||
+72
-43
@@ -57,8 +57,8 @@ describe('security issues', function () {
|
||||
functionCalls.push('called');
|
||||
},
|
||||
});
|
||||
}).to.throw(Error);
|
||||
expect(functionCalls.length).to.equal(0);
|
||||
}).toThrow();
|
||||
expect(functionCalls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
|
||||
@@ -96,7 +96,7 @@ describe('security issues', function () {
|
||||
},
|
||||
{ allowCallsToHelperMissing: true }
|
||||
);
|
||||
equals(functionCalls.length, 1);
|
||||
expect(functionCalls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function () {
|
||||
@@ -109,20 +109,23 @@ describe('security issues', function () {
|
||||
});
|
||||
|
||||
describe('GH-1563', function () {
|
||||
it('should not allow to access constructor after overriding via __defineGetter__', function () {
|
||||
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
|
||||
return this.skip(); // Browser does not support this exploit anyway
|
||||
var browserSupportsExploit =
|
||||
{}.__defineGetter__ != null && {}.__lookupGetter__ != null;
|
||||
|
||||
it.skipIf(!browserSupportsExploit)(
|
||||
'should not allow to access constructor after overriding via __defineGetter__',
|
||||
function () {
|
||||
expectTemplate(
|
||||
'{{__defineGetter__ "undefined" valueOf }}' +
|
||||
'{{#with __lookupGetter__ }}' +
|
||||
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
|
||||
'{{constructor.name}}' +
|
||||
'{{/with}}'
|
||||
)
|
||||
.withInput({})
|
||||
.toThrow(/Missing helper: "__defineGetter__"/);
|
||||
}
|
||||
expectTemplate(
|
||||
'{{__defineGetter__ "undefined" valueOf }}' +
|
||||
'{{#with __lookupGetter__ }}' +
|
||||
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
|
||||
'{{constructor.name}}' +
|
||||
'{{/with}}'
|
||||
)
|
||||
.withInput({})
|
||||
.toThrow(/Missing helper: "__defineGetter__"/);
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
describe('GH-1595: dangerous properties', function () {
|
||||
@@ -169,7 +172,7 @@ describe('security issues', function () {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('control access to prototype methods via "allowedProtoMethods"', function () {
|
||||
@@ -181,19 +184,25 @@ describe('security issues', function () {
|
||||
|
||||
function checkProtoMethodAccess(compileOptions) {
|
||||
it('should be prohibited by default and log a warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.calledOnce).to.be.true();
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
/Handlebars: Access has been denied/
|
||||
);
|
||||
});
|
||||
|
||||
it('should only log the warning once', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -205,12 +214,16 @@ describe('security issues', function () {
|
||||
.withCompileOptions(compileOptions)
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.calledOnce).to.be.true();
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
/Handlebars: Access has been denied/
|
||||
);
|
||||
});
|
||||
|
||||
it('can be allowed, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -222,11 +235,13 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('returnValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned on by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -236,11 +251,13 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('returnValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned off by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aMethod}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -250,7 +267,7 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned off, if turned on by default', function () {
|
||||
@@ -300,19 +317,25 @@ describe('security issues', function () {
|
||||
|
||||
function checkProtoPropertyAccess(compileOptions) {
|
||||
it('should be prohibited by default and log a warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
.withCompileOptions(compileOptions)
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.calledOnce).to.be.true();
|
||||
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
/Handlebars: Access has been denied/
|
||||
);
|
||||
});
|
||||
|
||||
it('can be explicitly prohibited by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -322,11 +345,13 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned on, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -338,11 +363,13 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('propertyValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned on by default, which disables the warning', function () {
|
||||
var spy = sinon.spy(console, 'error');
|
||||
var spy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(function () {});
|
||||
|
||||
expectTemplate('{{aProperty}}')
|
||||
.withInput(new TestClass())
|
||||
@@ -352,7 +379,7 @@ describe('security issues', function () {
|
||||
})
|
||||
.toCompileTo('propertyValue');
|
||||
|
||||
expect(spy.callCount).to.equal(0);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can be turned off, if turned on by default', function () {
|
||||
@@ -373,14 +400,16 @@ describe('security issues', function () {
|
||||
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function () {
|
||||
beforeEach(function simulateRuntimeWithoutLookupProperty() {
|
||||
var oldTemplateMethod = handlebarsEnv.template;
|
||||
sinon.replace(handlebarsEnv, 'template', function (templateSpec) {
|
||||
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
|
||||
return oldTemplateMethod.call(this, templateSpec);
|
||||
});
|
||||
vi.spyOn(handlebarsEnv, 'template').mockImplementation(
|
||||
function (templateSpec) {
|
||||
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
|
||||
return oldTemplateMethod.call(this, templateSpec);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should work with simple properties', function () {
|
||||
|
||||
+17
-11
@@ -1,9 +1,10 @@
|
||||
import { createRequire } from 'module';
|
||||
|
||||
var SourceMap, SourceMapConsumer;
|
||||
try {
|
||||
if (typeof define !== 'function' || !define.amd) {
|
||||
var SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
}
|
||||
} catch (err) {
|
||||
SourceMap = createRequire(import.meta.url)('source-map');
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
} catch {
|
||||
/* NOP for in browser */
|
||||
}
|
||||
|
||||
@@ -18,10 +19,14 @@ describe('source-map', function () {
|
||||
srcName: 'src.hbs',
|
||||
});
|
||||
|
||||
equal(!!template.code, true);
|
||||
equal(!!template.map, !CompilerContext.browser);
|
||||
expect(template.code).toBeTruthy();
|
||||
if (CompilerContext.browser) {
|
||||
expect(template.map).toBeFalsy();
|
||||
} else {
|
||||
expect(template.map).toBeTruthy();
|
||||
}
|
||||
});
|
||||
it('should map source properly', function () {
|
||||
it('should map source properly', async function () {
|
||||
var templateSource =
|
||||
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
|
||||
template = Handlebars.precompile(templateSource, {
|
||||
@@ -30,15 +35,16 @@ describe('source-map', function () {
|
||||
});
|
||||
|
||||
if (template.map) {
|
||||
var consumer = new SourceMapConsumer(template.map),
|
||||
var consumer = await new SourceMapConsumer(template.map),
|
||||
lines = template.code.split('\n'),
|
||||
srcLines = templateSource.split('\n'),
|
||||
generated = grepLine('" b"', lines),
|
||||
source = grepLine(' b', srcLines);
|
||||
|
||||
var mapped = consumer.originalPositionFor(generated);
|
||||
equal(mapped.line, source.line);
|
||||
equal(mapped.column, source.column);
|
||||
expect(mapped.line).toBe(source.line);
|
||||
expect(mapped.column).toBe(source.column);
|
||||
consumer.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+7
-3
@@ -1,16 +1,20 @@
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
describe('spec', function () {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var specDir = __dirname + '/mustache/specs/';
|
||||
var specs = fs.readdirSync(specDir).filter((name) => /.*\.json$/.test(name));
|
||||
|
||||
specs.forEach(function (name) {
|
||||
var spec = require(specDir + name);
|
||||
var spec = JSON.parse(fs.readFileSync(specDir + name, 'utf8'));
|
||||
spec.tests.forEach(function (test) {
|
||||
// Our lambda implementation knowingly deviates from the optional Mustache lambda spec
|
||||
// We also do not support alternative delimiters
|
||||
|
||||
+63
-6
@@ -92,8 +92,8 @@ describe('strict', function () {
|
||||
})
|
||||
.withHelpers({
|
||||
helper: function (options) {
|
||||
equals('value' in options.hash, true);
|
||||
equals(options.hash.value, undefined);
|
||||
expect(options.hash).toHaveProperty('value');
|
||||
expect(options.hash.value).toBeUndefined();
|
||||
return 'success';
|
||||
},
|
||||
})
|
||||
@@ -113,14 +113,71 @@ describe('strict', function () {
|
||||
});
|
||||
template({});
|
||||
} catch (error) {
|
||||
equals(error.lineNumber, 4);
|
||||
equals(error.endLineNumber, 4);
|
||||
equals(error.column, 5);
|
||||
equals(error.endColumn, 10);
|
||||
expect(error.lineNumber).toBe(4);
|
||||
expect(error.endLineNumber).toBe(4);
|
||||
expect(error.column).toBe(5);
|
||||
expect(error.endColumn).toBe(10);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('strict and compat mode', function () {
|
||||
it('GH-1741: should render a simple variable', function () {
|
||||
expectTemplate('{{v}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ v: 'a' })
|
||||
.toCompileTo('a');
|
||||
});
|
||||
|
||||
it('should throw for a missing variable', function () {
|
||||
expectTemplate('{{v}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.toThrow(Exception, /"v" not defined in/);
|
||||
});
|
||||
|
||||
it('GH-2149: should render correctly when block context is a boolean', function () {
|
||||
expectTemplate('{{#foo}}Hello {{bar}}{{/foo}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ foo: true, bar: 'World' })
|
||||
.toCompileTo('Hello World');
|
||||
});
|
||||
|
||||
it('GH-2149: should render correctly when looking up a property on each item', function () {
|
||||
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ items: [{ name: 'Hello' }] })
|
||||
.toCompileTo('Hello');
|
||||
});
|
||||
|
||||
it('should still throw when a property is missing at all depths', function () {
|
||||
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ items: [1, 2] })
|
||||
.toThrow(Exception, /"name" not defined in/);
|
||||
});
|
||||
|
||||
it('should still perform recursive lookup when a property is not in the current context', function () {
|
||||
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ name: 'root', items: [{}] })
|
||||
.toCompileTo('root');
|
||||
});
|
||||
|
||||
it('should still perform recursive lookup with a multi-part path not in context', function () {
|
||||
expectTemplate('{{#with child}}{{name.first}}{{/with}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ name: { first: 'root' }, child: { name: null } })
|
||||
.toCompileTo('root');
|
||||
});
|
||||
|
||||
it('should directly return an explicitly null property', function () {
|
||||
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||
.withCompileOptions({ strict: true, compat: true })
|
||||
.withInput({ name: 'root', items: [{ name: null }] })
|
||||
.toCompileTo('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('assume objects', function () {
|
||||
it('should ignore missing property', function () {
|
||||
expectTemplate('{{hello}}')
|
||||
|
||||
@@ -1,796 +0,0 @@
|
||||
function shouldMatchTokens(result, tokens) {
|
||||
for (var index = 0; index < result.length; index++) {
|
||||
equals(result[index].name, tokens[index]);
|
||||
}
|
||||
}
|
||||
function shouldBeToken(result, name, text) {
|
||||
equals(result.name, name);
|
||||
equals(result.text, text);
|
||||
}
|
||||
|
||||
describe('Tokenizer', function () {
|
||||
if (!Handlebars.Parser) {
|
||||
return;
|
||||
}
|
||||
|
||||
function tokenize(template) {
|
||||
var parser = Handlebars.Parser,
|
||||
lexer = parser.lexer;
|
||||
|
||||
lexer.setInput(template);
|
||||
var out = [],
|
||||
token;
|
||||
|
||||
while ((token = lexer.lex())) {
|
||||
var result = parser.terminals_[token] || token;
|
||||
if (!result || result === 'EOF' || result === 'INVALID') {
|
||||
break;
|
||||
}
|
||||
out.push({ name: result, text: lexer.yytext });
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function () {
|
||||
var result = tokenize('{{foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('supports unescaping with &', function () {
|
||||
var result = tokenize('{{&bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[0], 'OPEN', '{{&');
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports unescaping with {{{', function () {
|
||||
var result = tokenize('{{{bar}}}');
|
||||
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
|
||||
shouldBeToken(result[1], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping delimiters', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
});
|
||||
|
||||
it('supports escaping multiple delimiters', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' ');
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaping a triple stash', function () {
|
||||
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
|
||||
});
|
||||
|
||||
it('supports escaping escape character', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('supports escaping multiple escape characters', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[9], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped mustaches after escaped escape characters', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[4], 'OPEN', '{{');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
shouldBeToken(result[7], 'CONTENT', ' ');
|
||||
shouldBeToken(result[8], 'CONTENT', '{{baz}}');
|
||||
});
|
||||
|
||||
it('supports escaped escape characters after escaped mustaches', function () {
|
||||
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
|
||||
shouldBeToken(result[5], 'CONTENT', '\\');
|
||||
shouldBeToken(result[6], 'OPEN', '{{');
|
||||
shouldBeToken(result[7], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('supports escaped escape character on a triple stash', function () {
|
||||
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_UNESCAPED',
|
||||
'ID',
|
||||
'CLOSE_UNESCAPED',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
shouldBeToken(result[3], 'CONTENT', ' \\');
|
||||
shouldBeToken(result[5], 'ID', 'bar');
|
||||
});
|
||||
|
||||
it('tokenizes a simple path', function () {
|
||||
var result = tokenize('{{foo/bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows dot notation', function () {
|
||||
var result = tokenize('{{foo.bar}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
|
||||
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows path literals with []', function () {
|
||||
var result = tokenize('{{foo.[bar]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows multiple path literals on a line with []', function () {
|
||||
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows escaped literals in []', function () {
|
||||
var result = tokenize('{{foo.[bar\\]]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function () {
|
||||
var result = tokenize('{{.}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function () {
|
||||
var result = tokenize('{{../foo/bar}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with .. as a parent path', function () {
|
||||
var result = tokenize('{{../foo.bar}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', '..');
|
||||
});
|
||||
|
||||
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function () {
|
||||
var result = tokenize('{{this/foo}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'this');
|
||||
shouldBeToken(result[3], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo \n bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes raw content as "CONTENT"', function () {
|
||||
var result = tokenize('foo {{ bar }} baz');
|
||||
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
|
||||
shouldBeToken(result[0], 'CONTENT', 'foo ');
|
||||
shouldBeToken(result[4], 'CONTENT', ' baz');
|
||||
});
|
||||
|
||||
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{> foo bar }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function () {
|
||||
var result = tokenize('{{>foo/bar.baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_PARTIAL',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'SEP',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes partial block declarations', function () {
|
||||
var result = tokenize('{{#> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL_BLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
it('tokenizes a comment as "COMMENT"', function () {
|
||||
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment as "COMMENT"', function () {
|
||||
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
|
||||
});
|
||||
|
||||
it('tokenizes a block comment with whitespace as "COMMENT"', function () {
|
||||
var result = tokenize(
|
||||
'foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'CONTENT',
|
||||
'COMMENT',
|
||||
'CONTENT',
|
||||
'OPEN',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
|
||||
});
|
||||
|
||||
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function () {
|
||||
var result = tokenize('{{#foo}}content{{/foo}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_ENDBLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes directives', function () {
|
||||
shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
'CONTENT',
|
||||
'OPEN_ENDBLOCK',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "INVERSE"', function () {
|
||||
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{ else }}'), ['INVERSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function () {
|
||||
var result = tokenize('{{^foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function () {
|
||||
var result = tokenize('{{^ foo }}');
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function () {
|
||||
var result = tokenize('{{ foo bar baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[2], 'ID', 'bar');
|
||||
shouldBeToken(result[3], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function () {
|
||||
var result = tokenize('{{ foo bar "baz" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function () {
|
||||
var result = tokenize("{{ foo bar 'baz' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params with spaces inside as "STRING"', function () {
|
||||
var result = tokenize('{{ foo bar "baz bat" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz bat');
|
||||
});
|
||||
|
||||
it('tokenizes String params with escapes quotes as STRING', function () {
|
||||
var result = tokenize('{{ foo "bar\\"baz" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'STRING', 'bar"baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params using single quotes with escapes quotes as STRING', function () {
|
||||
var result = tokenize("{{ foo 'bar\\'baz' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'STRING', "bar'baz");
|
||||
});
|
||||
|
||||
it('tokenizes numbers', function () {
|
||||
var result = tokenize('{{ foo 1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '1');
|
||||
|
||||
result = tokenize('{{ foo 1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '1.1');
|
||||
|
||||
result = tokenize('{{ foo -1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '-1');
|
||||
|
||||
result = tokenize('{{ foo -1.1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'NUMBER', '-1.1');
|
||||
});
|
||||
|
||||
it('tokenizes booleans', function () {
|
||||
var result = tokenize('{{ foo true }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'true');
|
||||
|
||||
result = tokenize('{{ foo false }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'BOOLEAN', 'false');
|
||||
});
|
||||
|
||||
it('tokenizes undefined and null', function () {
|
||||
var result = tokenize('{{ foo undefined null }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'UNDEFINED', 'NULL', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'UNDEFINED', 'undefined');
|
||||
shouldBeToken(result[3], 'NULL', 'null');
|
||||
});
|
||||
|
||||
it('tokenizes hash arguments', function () {
|
||||
var result = tokenize('{{ foo bar=baz }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize('{{ foo bar baz=bat }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=1 }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'NUMBER',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=true }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'BOOLEAN',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz=false }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'BOOLEAN',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar\n baz=bat }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz="bat" }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{ foo bar baz="bat" bam=wot }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{foo omg bar=baz bat="bam"}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'STRING',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[2], 'ID', 'omg');
|
||||
});
|
||||
|
||||
it('tokenizes special @ identifiers', function () {
|
||||
var result = tokenize('{{ @foo }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[2], 'ID', 'foo');
|
||||
|
||||
result = tokenize('{{ foo @bar }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize('{{ foo bar=@baz }}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'ID',
|
||||
'EQUALS',
|
||||
'DATA',
|
||||
'ID',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[5], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('does not time out in a mustache with a single } followed by EOF', function () {
|
||||
shouldMatchTokens(tokenize('{{foo}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('does not time out in a mustache when invalid ID characters are used', function () {
|
||||
shouldMatchTokens(tokenize('{{foo & }}'), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('tokenizes subexpressions', function () {
|
||||
var result = tokenize('{{foo (bar)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
|
||||
result = tokenize('{{foo (a-x b-y)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[1], 'ID', 'foo');
|
||||
shouldBeToken(result[3], 'ID', 'a-x');
|
||||
shouldBeToken(result[4], 'ID', 'b-y');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions', function () {
|
||||
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
shouldBeToken(result[3], 'ID', 'bar');
|
||||
shouldBeToken(result[5], 'ID', 'lol');
|
||||
shouldBeToken(result[6], 'ID', 'rofl');
|
||||
shouldBeToken(result[10], 'ID', 'baz');
|
||||
});
|
||||
|
||||
it('tokenizes nested subexpressions: literals', function () {
|
||||
var result = tokenize(
|
||||
'{{foo (bar (lol true) false) (baz 1) (blah \'b\') (blorg "c")}}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'BOOLEAN',
|
||||
'CLOSE_SEXPR',
|
||||
'BOOLEAN',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'NUMBER',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'STRING',
|
||||
'CLOSE_SEXPR',
|
||||
'OPEN_SEXPR',
|
||||
'ID',
|
||||
'STRING',
|
||||
'CLOSE_SEXPR',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes block params', function () {
|
||||
var result = tokenize('{{#foo as |bar|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as | bar baz |}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{#foo as as | bar baz |}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_BLOCK',
|
||||
'ID',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
|
||||
result = tokenize('{{else foo as |bar baz|}}');
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_INVERSE_CHAIN',
|
||||
'ID',
|
||||
'OPEN_BLOCK_PARAMS',
|
||||
'ID',
|
||||
'ID',
|
||||
'CLOSE_BLOCK_PARAMS',
|
||||
'CLOSE',
|
||||
]);
|
||||
});
|
||||
|
||||
it('tokenizes raw blocks', function () {
|
||||
var result = tokenize(
|
||||
'{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'
|
||||
);
|
||||
shouldMatchTokens(result, [
|
||||
'OPEN_RAW_BLOCK',
|
||||
'ID',
|
||||
'CLOSE_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'END_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'OPEN_RAW_BLOCK',
|
||||
'ID',
|
||||
'CLOSE_RAW_BLOCK',
|
||||
'CONTENT',
|
||||
'END_RAW_BLOCK',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars.template, true);
|
||||
equal(!!Handlebars.VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
// Reporting to test-runner
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="/node_modules/chai/chai.js"></script>
|
||||
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/spec/vendor/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
|
||||
require(['handlebars'], function(Handlebars) {
|
||||
window.Handlebars = Handlebars;
|
||||
|
||||
require(['tests'], function() {
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
// Reporting to test-runner
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+40
-28
@@ -5,11 +5,7 @@ describe('utils', function () {
|
||||
if (!(safe instanceof Handlebars.SafeString)) {
|
||||
throw new Error('Must be instance of SafeString');
|
||||
}
|
||||
equals(
|
||||
safe.toString(),
|
||||
'testing 1, 2, 3',
|
||||
'SafeString is equivalent to its underlying string'
|
||||
);
|
||||
expect(safe.toString()).toBe('testing 1, 2, 3');
|
||||
});
|
||||
|
||||
it('it should not escape SafeString properties', function () {
|
||||
@@ -23,51 +19,50 @@ describe('utils', function () {
|
||||
|
||||
describe('#escapeExpression', function () {
|
||||
it('should escape html', function () {
|
||||
equals(
|
||||
Handlebars.Utils.escapeExpression('foo<&"\'>'),
|
||||
expect(Handlebars.Utils.escapeExpression('foo<&"\'>')).toBe(
|
||||
'foo<&"'>'
|
||||
);
|
||||
equals(Handlebars.Utils.escapeExpression('foo='), 'foo=');
|
||||
expect(Handlebars.Utils.escapeExpression('foo=')).toBe('foo=');
|
||||
});
|
||||
it('should not escape SafeString', function () {
|
||||
var string = new Handlebars.SafeString('foo<&"\'>');
|
||||
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
|
||||
expect(Handlebars.Utils.escapeExpression(string)).toBe('foo<&"\'>');
|
||||
|
||||
var obj = {
|
||||
toHTML: function () {
|
||||
return 'foo<&"\'>';
|
||||
},
|
||||
};
|
||||
equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>');
|
||||
expect(Handlebars.Utils.escapeExpression(obj)).toBe('foo<&"\'>');
|
||||
});
|
||||
it('should handle falsy', function () {
|
||||
equals(Handlebars.Utils.escapeExpression(''), '');
|
||||
equals(Handlebars.Utils.escapeExpression(undefined), '');
|
||||
equals(Handlebars.Utils.escapeExpression(null), '');
|
||||
expect(Handlebars.Utils.escapeExpression('')).toBe('');
|
||||
expect(Handlebars.Utils.escapeExpression(undefined)).toBe('');
|
||||
expect(Handlebars.Utils.escapeExpression(null)).toBe('');
|
||||
|
||||
equals(Handlebars.Utils.escapeExpression(false), 'false');
|
||||
equals(Handlebars.Utils.escapeExpression(0), '0');
|
||||
expect(Handlebars.Utils.escapeExpression(false)).toBe('false');
|
||||
expect(Handlebars.Utils.escapeExpression(0)).toBe('0');
|
||||
});
|
||||
it('should handle empty objects', function () {
|
||||
equals(Handlebars.Utils.escapeExpression({}), {}.toString());
|
||||
equals(Handlebars.Utils.escapeExpression([]), [].toString());
|
||||
expect(Handlebars.Utils.escapeExpression({})).toBe({}.toString());
|
||||
expect(Handlebars.Utils.escapeExpression([])).toBe([].toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isEmpty', function () {
|
||||
it('should not be empty', function () {
|
||||
equals(Handlebars.Utils.isEmpty(undefined), true);
|
||||
equals(Handlebars.Utils.isEmpty(null), true);
|
||||
equals(Handlebars.Utils.isEmpty(false), true);
|
||||
equals(Handlebars.Utils.isEmpty(''), true);
|
||||
equals(Handlebars.Utils.isEmpty([]), true);
|
||||
expect(Handlebars.Utils.isEmpty(undefined)).toBe(true);
|
||||
expect(Handlebars.Utils.isEmpty(null)).toBe(true);
|
||||
expect(Handlebars.Utils.isEmpty(false)).toBe(true);
|
||||
expect(Handlebars.Utils.isEmpty('')).toBe(true);
|
||||
expect(Handlebars.Utils.isEmpty([])).toBe(true);
|
||||
});
|
||||
|
||||
it('should be empty', function () {
|
||||
equals(Handlebars.Utils.isEmpty(0), false);
|
||||
equals(Handlebars.Utils.isEmpty([1]), false);
|
||||
equals(Handlebars.Utils.isEmpty('foo'), false);
|
||||
equals(Handlebars.Utils.isEmpty({ bar: 1 }), false);
|
||||
expect(Handlebars.Utils.isEmpty(0)).toBe(false);
|
||||
expect(Handlebars.Utils.isEmpty([1])).toBe(false);
|
||||
expect(Handlebars.Utils.isEmpty('foo')).toBe(false);
|
||||
expect(Handlebars.Utils.isEmpty({ bar: 1 })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,8 +77,25 @@ describe('utils', function () {
|
||||
|
||||
Handlebars.Utils.extend(b, new A());
|
||||
|
||||
equals(b.a, 1);
|
||||
equals(b.b, 2);
|
||||
expect(b.a).toBe(1);
|
||||
expect(b.b).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isType', function () {
|
||||
it('should check if variable is type Array', function () {
|
||||
expect(Handlebars.Utils.isArray('string')).toBe(false);
|
||||
expect(Handlebars.Utils.isArray([])).toBe(true);
|
||||
});
|
||||
|
||||
it('should check if variable is type Map', function () {
|
||||
expect(Handlebars.Utils.isMap('string')).toBe(false);
|
||||
expect(Handlebars.Utils.isMap(new Map())).toBe(true);
|
||||
});
|
||||
|
||||
it('should check if variable is type Set', function () {
|
||||
expect(Handlebars.Utils.isSet('string')).toBe(false);
|
||||
expect(Handlebars.Utils.isSet(new Set())).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
-2054
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-process-env': 'off',
|
||||
'prefer-const': 'warn',
|
||||
'compat/compat': 'off',
|
||||
'dot-notation': ['error', { allowKeywords: true }],
|
||||
},
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
const metrics = require('../tests/bench');
|
||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
||||
|
||||
module.exports = function (grunt) {
|
||||
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
|
||||
|
||||
registerAsyncTask('metrics', function () {
|
||||
const onlyExecuteName = grunt.option('name');
|
||||
const events = {};
|
||||
|
||||
const promises = Object.keys(metrics).map(async (name) => {
|
||||
if (/^_/.test(name)) {
|
||||
return;
|
||||
}
|
||||
if (onlyExecuteName != null && name !== onlyExecuteName) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
metrics[name](grunt, function (data) {
|
||||
events[name] = data;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
};
|
||||
+102
-88
@@ -1,102 +1,116 @@
|
||||
const AWS = require('aws-sdk');
|
||||
const git = require('./util/git');
|
||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
||||
const semver = require('semver');
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'fs';
|
||||
import { S3, PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import * as git from './util/git.js';
|
||||
import semver from 'semver';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
|
||||
export const PUBLISHED_FILES = [
|
||||
'handlebars.js',
|
||||
'handlebars.min.js',
|
||||
'handlebars.runtime.js',
|
||||
'handlebars.runtime.min.js',
|
||||
];
|
||||
|
||||
registerAsyncTask('publish-to-aws', async () => {
|
||||
grunt.log.writeln('remotes: ' + (await git.remotes()));
|
||||
grunt.log.writeln('branches: ' + (await git.branches()));
|
||||
let s3Client;
|
||||
|
||||
const commitInfo = await git.commitInfo();
|
||||
grunt.log.writeln('tag: ', commitInfo.tagName);
|
||||
async function main() {
|
||||
console.log('remotes: ' + (await git.remotes()));
|
||||
console.log('branches: ' + (await git.branches()));
|
||||
|
||||
const suffixes = [];
|
||||
const commitInfo = await git.commitInfo();
|
||||
console.log('tag: ', commitInfo.tagName);
|
||||
|
||||
// Publish the master as "latest" and with the commit-id
|
||||
if (commitInfo.isMaster) {
|
||||
suffixes.push('-latest');
|
||||
suffixes.push('-' + commitInfo.headSha);
|
||||
}
|
||||
const suffixes = buildSuffixes(commitInfo);
|
||||
|
||||
// Publish tags by their tag-name
|
||||
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
|
||||
suffixes.push('-' + commitInfo.tagName);
|
||||
}
|
||||
|
||||
if (suffixes.length > 0) {
|
||||
initSDK();
|
||||
grunt.log.writeln(
|
||||
'publishing file-suffixes: ' + JSON.stringify(suffixes)
|
||||
);
|
||||
await publish(suffixes);
|
||||
}
|
||||
});
|
||||
|
||||
function initSDK() {
|
||||
const bucket = process.env.S3_BUCKET_NAME,
|
||||
key = process.env.S3_ACCESS_KEY_ID,
|
||||
secret = process.env.S3_SECRET_ACCESS_KEY;
|
||||
|
||||
if (!bucket || !key || !secret) {
|
||||
throw new Error('Missing S3 config values');
|
||||
}
|
||||
|
||||
AWS.config.update({ accessKeyId: key, secretAccessKey: secret });
|
||||
if (suffixes.length > 0) {
|
||||
validateS3Env();
|
||||
console.log('publishing file-suffixes: ' + JSON.stringify(suffixes));
|
||||
await publish(suffixes);
|
||||
}
|
||||
|
||||
async function publish(suffixes) {
|
||||
const publishPromises = suffixes.map((suffix) => publishSuffix(suffix));
|
||||
return Promise.all(publishPromises);
|
||||
}
|
||||
|
||||
async function publishSuffix(suffix) {
|
||||
const filenames = [
|
||||
'handlebars.js',
|
||||
'handlebars.min.js',
|
||||
'handlebars.runtime.js',
|
||||
'handlebars.runtime.min.js',
|
||||
];
|
||||
const publishPromises = filenames.map(async (filename) => {
|
||||
const nameInBucket = getNameInBucket(filename, suffix);
|
||||
const localFile = getLocalFile(filename);
|
||||
await uploadToBucket(localFile, nameInBucket);
|
||||
grunt.log.writeln(
|
||||
`Published ${localFile} to build server (${nameInBucket})`
|
||||
);
|
||||
});
|
||||
return Promise.all(publishPromises);
|
||||
}
|
||||
|
||||
async function uploadToBucket(localFile, nameInBucket) {
|
||||
const bucket = process.env.S3_BUCKET_NAME;
|
||||
const uploadParams = {
|
||||
Bucket: bucket,
|
||||
Key: nameInBucket,
|
||||
Body: grunt.file.read(localFile),
|
||||
};
|
||||
return s3PutObject(uploadParams);
|
||||
}
|
||||
};
|
||||
|
||||
function s3PutObject(uploadParams) {
|
||||
const s3 = new AWS.S3();
|
||||
return new Promise((resolve, reject) => {
|
||||
s3.putObject(uploadParams, (err) => {
|
||||
if (err != null) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getNameInBucket(filename, suffix) {
|
||||
export function buildSuffixes(commitInfo) {
|
||||
const suffixes = [];
|
||||
|
||||
if (commitInfo.isMaster) {
|
||||
suffixes.push('-latest');
|
||||
suffixes.push('-' + commitInfo.headSha);
|
||||
}
|
||||
|
||||
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
|
||||
suffixes.push('-' + commitInfo.tagName);
|
||||
}
|
||||
|
||||
return suffixes;
|
||||
}
|
||||
|
||||
export function validateS3Env() {
|
||||
const bucket = process.env.S3_BUCKET_NAME,
|
||||
region = process.env.S3_REGION,
|
||||
key = process.env.S3_ACCESS_KEY_ID,
|
||||
secret = process.env.S3_SECRET_ACCESS_KEY;
|
||||
|
||||
if (!bucket || !region || !key || !secret) {
|
||||
throw new Error('Missing S3 config values');
|
||||
}
|
||||
}
|
||||
|
||||
export async function publish(suffixes, overrides) {
|
||||
const publishPromises = suffixes.map((suffix) =>
|
||||
publishSuffix(suffix, overrides)
|
||||
);
|
||||
return Promise.all(publishPromises);
|
||||
}
|
||||
|
||||
async function publishSuffix(suffix, overrides) {
|
||||
const publishPromises = PUBLISHED_FILES.map(async (filename) => {
|
||||
const nameInBucket = getNameInBucket(filename, suffix);
|
||||
const localFile = getLocalFile(filename);
|
||||
await uploadToBucket(localFile, nameInBucket, overrides);
|
||||
console.log(`Published ${localFile} to build server (${nameInBucket})`);
|
||||
});
|
||||
return Promise.all(publishPromises);
|
||||
}
|
||||
|
||||
async function uploadToBucket(localFile, nameInBucket, overrides) {
|
||||
const s3 = overrides?.s3Client ?? getS3Client();
|
||||
const bucket = overrides?.bucket ?? process.env.S3_BUCKET_NAME;
|
||||
|
||||
return s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: nameInBucket,
|
||||
Body: fs.readFileSync(localFile, 'utf8'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getS3Client() {
|
||||
if (!s3Client) {
|
||||
s3Client = new S3({
|
||||
region: process.env.S3_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
}
|
||||
return s3Client;
|
||||
}
|
||||
|
||||
export function getNameInBucket(filename, suffix) {
|
||||
return filename.replace(/\.js$/, suffix + '.js');
|
||||
}
|
||||
|
||||
function getLocalFile(filename) {
|
||||
export function getLocalFile(filename) {
|
||||
return 'dist/' + filename;
|
||||
}
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const chai = require('chai');
|
||||
chai.use(require('chai-diff'));
|
||||
const expect = chai.expect;
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
binInputParameters: ['-a', 'spec/artifacts/empty.handlebars'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-a',
|
||||
'-f',
|
||||
'TEST_OUTPUT',
|
||||
'spec/artifacts/empty.handlebars',
|
||||
],
|
||||
outputLocation: 'TEST_OUTPUT',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-a',
|
||||
'-n',
|
||||
'CustomNamespace.templates',
|
||||
'spec/artifacts/empty.handlebars',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.namespace.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-a',
|
||||
'--namespace',
|
||||
'CustomNamespace.templates',
|
||||
'spec/artifacts/empty.handlebars',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.namespace.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['-a', '-s', 'spec/artifacts/empty.handlebars'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.simple.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['-a', '-m', 'spec/artifacts/empty.handlebars'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.amd.min.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'spec/artifacts/known.helpers.handlebars',
|
||||
'-a',
|
||||
'-k',
|
||||
'someHelper',
|
||||
'-k',
|
||||
'anotherHelper',
|
||||
'-o',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/non.empty.amd.known.helper.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['--help'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/help.menu.txt',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['-v'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutput: require('../package.json').version,
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-a',
|
||||
'-e',
|
||||
'hbs',
|
||||
'./spec/artifacts/non.default.extension.hbs',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/non.default.extension.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-a',
|
||||
'-p',
|
||||
'./spec/artifacts/partial.template.handlebars',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/partial.template.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['spec/artifacts/empty.handlebars', '-c'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.common.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'spec/artifacts/empty.handlebars',
|
||||
'spec/artifacts/empty.handlebars',
|
||||
'-a',
|
||||
'-n',
|
||||
'someNameSpace',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/namespace.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'spec/artifacts/empty.handlebars',
|
||||
'-h',
|
||||
'some-path/',
|
||||
'-a',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/handlebar.path.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'spec/artifacts/partial.template.handlebars',
|
||||
'-r',
|
||||
'spec',
|
||||
'-a',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.root.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-i',
|
||||
'<div>1</div>',
|
||||
'-i',
|
||||
'<div>2</div>',
|
||||
'-N',
|
||||
'firstTemplate',
|
||||
'-N',
|
||||
'secondTemplate',
|
||||
'-a',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.name.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: [
|
||||
'-i',
|
||||
'<div>1</div>',
|
||||
'-a',
|
||||
'-m',
|
||||
'-N',
|
||||
'test',
|
||||
'--map',
|
||||
'./spec/tmp/source.map.amd.txt',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/source.map.amd.js',
|
||||
},
|
||||
{
|
||||
binInputParameters: ['./spec/artifacts/bom.handlebars', '-b', '-a'],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/bom.amd.js',
|
||||
},
|
||||
// Issue #1673
|
||||
{
|
||||
binInputParameters: [
|
||||
'--amd',
|
||||
'--no-amd',
|
||||
'spec/artifacts/empty.handlebars',
|
||||
],
|
||||
outputLocation: 'stdout',
|
||||
expectedOutputSpec: './spec/expected/empty.common.js',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.registerTask('test:bin', function () {
|
||||
testCases.forEach(
|
||||
({
|
||||
binInputParameters,
|
||||
outputLocation,
|
||||
expectedOutputSpec,
|
||||
expectedOutput,
|
||||
}) => {
|
||||
const stdout = executeBinHandlebars(...binInputParameters);
|
||||
|
||||
if (!expectedOutput && expectedOutputSpec) {
|
||||
expectedOutput = fs.readFileSync(expectedOutputSpec, 'utf-8');
|
||||
}
|
||||
|
||||
const useStdout = outputLocation === 'stdout';
|
||||
const normalizedOutput = normalizeCrlf(
|
||||
useStdout ? stdout : fs.readFileSync(outputLocation, 'utf-8')
|
||||
);
|
||||
const normalizedExpectedOutput = normalizeCrlf(expectedOutput);
|
||||
|
||||
if (!useStdout) {
|
||||
fs.unlinkSync(outputLocation);
|
||||
}
|
||||
|
||||
expect(normalizedOutput).not.to.be.differentFrom(
|
||||
normalizedExpectedOutput,
|
||||
{
|
||||
relaxedSpace: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// helper functions
|
||||
|
||||
function executeBinHandlebars(...args) {
|
||||
if (os.platform() === 'win32') {
|
||||
// On Windows, the executable handlebars.js file cannot be run directly
|
||||
const nodeJs = process.argv[0];
|
||||
return execFilesSyncUtf8(nodeJs, ['./bin/handlebars.js'].concat(args));
|
||||
}
|
||||
return execFilesSyncUtf8('./bin/handlebars.js', args);
|
||||
}
|
||||
|
||||
function execFilesSyncUtf8(command, args) {
|
||||
const env = process.env;
|
||||
env.PATH = addPathToNodeJs(env.PATH);
|
||||
return childProcess.execFileSync(command, args, { encoding: 'utf-8', env });
|
||||
}
|
||||
|
||||
function addPathToNodeJs(pathEnvironment) {
|
||||
return path.dirname(process.argv0) + path.delimiter + pathEnvironment;
|
||||
}
|
||||
|
||||
function normalizeCrlf(string) {
|
||||
if (typeof string === 'string') {
|
||||
return string.replace(/\r\n/g, '\n');
|
||||
}
|
||||
return string;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
const { execNodeJsScriptWithInheritedOutput } = require('./util/exec-file');
|
||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
||||
const nodeJs = process.argv0;
|
||||
|
||||
module.exports = function (grunt) {
|
||||
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
|
||||
|
||||
registerAsyncTask('test:mocha', async () =>
|
||||
execNodeJsScriptWithInheritedOutput('./spec/env/runner')
|
||||
);
|
||||
|
||||
registerAsyncTask('test:cov', async () =>
|
||||
execNodeJsScriptWithInheritedOutput('node_modules/nyc/bin/nyc', [
|
||||
nodeJs,
|
||||
'./spec/env/runner.js',
|
||||
])
|
||||
);
|
||||
|
||||
registerAsyncTask('test:min', async () =>
|
||||
execNodeJsScriptWithInheritedOutput('./spec/env/runner', ['--min'])
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user