diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b9d48d491 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*.js] +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.yml] +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.eslintignore b/.eslintignore index 714d84019..46c3a2d3a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,6 @@ *.sublime-project *.sublime-workspace npm-debug.log -sauce_connect.log* .idea yarn-error.log node_modules @@ -12,10 +11,9 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ -/integration-testing/*/dist/ +/tests/integration/*/dist/ # Third-party or files that must remain unchanged /spec/expected/ diff --git a/.eslintrc.js b/.eslintrc.js index 2ac729358..f03eac32f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,14 @@ module.exports = { extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'], globals: { - self: false + self: false, }, env: { node: true, - es6: true + es2020: true, + }, + parserOptions: { + sourceType: 'module', }, rules: { 'no-console': 'warn', @@ -56,11 +59,6 @@ module.exports = { // ECMAScript 6 // //--------------// - 'no-var': 'error' + 'no-var': 'error', }, - parserOptions: { - sourceType: 'module', - ecmaVersion: 6, - ecmaFeatures: {} - } }; diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..a6ae06ee2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Upgrade to Prettier 2.7 +3d228334530860a6e3f99dc10777c84bf22292c1 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..6cd73dafe --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +Before filing issues, please check the following points first: + +- [ ] Please don't open issues for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars +- [ ] Have a look at https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md +- [ ] Read the FAQ at https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md +- [ ] Use the jsfiddle-template at https://jsfiddle.net/4nbwjaqz/4/ to reproduce problems or bugs + +This will probably help you to get a solution faster. +For bugs, it would be great to have a PR with a failing test-case. + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..7b0c5f177 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +Before creating a pull-request, please check https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md first. + +Generally we like to see pull requests that + +- [ ] Please don't start pull requests for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars +- [ ] Maintain the existing code style +- [ ] Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request) +- [ ] Have good commit messages +- [ ] 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. +- [ ] 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. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..c73c050af --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + open-pull-requests-limit: 0 + schedule: + interval: weekly + allow: + - dependency-type: production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..d7af2072c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,112 @@ +name: CI + +on: + push: + branches: + - master + pull_request: {} + +jobs: + lint: + name: Lint + runs-on: 'ubuntu-latest' + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - 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 }} + strategy: + fail-fast: false + matrix: + operating-system: ['ubuntu-latest', 'windows-latest'] + # https://nodejs.org/en/about/releases/ + node-version: ['12', '14', '16', '18', '20'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm ci + + - name: Test + run: npm run test + + - 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 - + + browser: + name: Test (Browser) + runs-on: 'ubuntu-latest' + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Install Playwright + run: | + npx playwright install-deps + npx playwright install + + - name: Build + run: npx grunt prepare + + - name: Test + run: npm run test:browser diff --git a/.gitignore b/.gitignore index 97e44f885..0674f3e77 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ *.sublime-project *.sublime-workspace npm-debug.log -sauce_connect.log* .idea /yarn-error.log /yarn.lock @@ -13,8 +12,7 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ -/integration-testing/*/dist/ -/spec/tmp/* \ No newline at end of file +/tests/integration/*/dist/ +/spec/tmp/* diff --git a/.gitmodules b/.gitmodules index 1739275a8..09cc7fdfd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "spec/mustache"] path = spec/mustache - url = git://github.com/mustache/spec.git + url = https://github.com/mustache/spec.git diff --git a/.prettierignore b/.prettierignore index 724620669..7c9924e64 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,10 +12,9 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ -/integration-testing/*/dist/ +/tests/integration/*/dist/ # Third-party or files that must remain unchanged /spec/expected/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e7d347e27..000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -language: node_js -jobs: - include: - - stage: test - name: check javascript (eslint) - node_js: lts/* - script: npm run lint - - stage: test - name: check formatting (prettier) - node_js: lts/* - script: npm run check-format - - stage: test - name: check typescript definitions (dtslint) - node_js: lts/* - script: npm run dtslint - - stage: test - name: extensive tests and publish to aws - script: npm run extensive-tests-and-publish-to-aws - env: - - S3_BUCKET_NAME=builds.handlebarsjs.com - - secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA= - - secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8= - - SAUCE_USERNAME=handlebars - - secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I= - node_js: '10' - - stage: test - name: test with latest nodejs-lts - node_js: lts/* - script: npm run test - - stage: test - name: test with active nodejs - node_js: node - script: npm run test -cache: npm -email: - on_failure: change - on_success: never -git: - depth: 100 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5386e6a23..9b6631195 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,28 @@ # How to Contribute +## Reporting security issues + +Please don't open issues for security issues. Instead, file a report at https://www.npmjs.com/advisories/report?package=handlebars + ## Reporting Issues -Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into. +Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into. -Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here) +Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! + +In general we are going to ask for an **example** of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle **[template][jsfiddle]** to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here). Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed. -Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site). +Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs). ## Branches - 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. ## Pull Requests @@ -47,9 +55,9 @@ You can also run our set of benchmarks with `grunt bench`. The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`. If you notice any problems, please report them to the GitHub issue tracker at -[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). +[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues). -##Running Tests +## Running Tests To run tests locally, first install all dependencies. @@ -78,14 +86,14 @@ We do linting and formatting in two phases: - Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden, while warnings are allowed. -- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings +- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings are forbidden. -You can use the following scripts to make sure that the travis-job does not fail: +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 travis does in its build-job, excluding the "integration-test". +- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test". - **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on) These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS). @@ -93,7 +101,7 @@ You can use the following scripts to make sure that the travis-job does not fail Before attempting the release Handlebars, please make sure that you have the following authorizations: -- Push-access to `wycats/handlebars.js` +- Push-access to `handlebars-lang/handlebars.js` - Publishing rights on npmjs.com for the `handlebars` package - Publishing rights on gemfury for the `handlebars-source` package - Push-access to the repo for legacy package managers: `components/handlebars` @@ -101,15 +109,12 @@ Before attempting the release Handlebars, please make sure that you have the fol _When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._ -Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks. - A full release may be completed with the following: ``` npm ci -yo release +npx grunt npm publish -yo release:publish components handlebars.js dist/components/ cd dist/components/ gem build handlebars-source.gemspec @@ -126,13 +131,13 @@ in those places still point to the latest version When everything is OK, the **handlebars site** needs to be updated. -Go to the master branch of the repo [handlebars-lang/handlebarsjs.com-github-pages](https://github.com/handlebars-lang/handlebarsjs.com-github-pages/tree/master) +Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master) and make a minimal change to the README. This will invoke a github-action that redeploys the site, fetching the latest version-number from the npm-registry. (note that the default-branch of this repo is not the master and regular changes are done in the `handlebars-lang/docs`-repo). [generator-release]: https://github.com/walmartlabs/generator-release -[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master -[issue]: https://github.com/wycats/handlebars.js/issues/new -[jsfiddle]: https://jsfiddle.net/9D88g/180/ +[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master +[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new +[jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/ diff --git a/FAQ.md b/FAQ.md index 108e839af..4edcb8ba4 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,22 +1,22 @@ # Frequently Asked Questions -1. How can I file a bug report: +## How can I file a bug report: - See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). + See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues). -1. Why isn't my Mustache template working? +## Why isn't my Mustache template working? - Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache). + Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache). -1. Why is it slower when compiling? +## Why is it slower when compiling? The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients. -1. Why doesn't this work with Content Security Policy restrictions? +## Why doesn't this work with Content Security Policy restrictions? When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime. -1. How can I include script tags in my template? +## How can I include script tags in my template? If loading the template via an inlined ` - - - - - - - - - - - - - -
- - diff --git a/spec/amd.html b/spec/amd.html deleted file mode 100644 index 1f783e02c..000000000 --- a/spec/amd.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - Mocha - - - - - - - - - - - - - - - - - - - - -
- - diff --git a/spec/ast.js b/spec/ast.js index 1f4146ae0..beb38a464 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -1,14 +1,14 @@ -describe('ast', function() { +describe('ast', function () { if (!Handlebars.AST) { return; } var AST = Handlebars.AST; - describe('BlockStatement', function() { - it('should throw on mustache mismatch', function() { + describe('BlockStatement', function () { + it('should throw on mustache mismatch', function () { shouldThrow( - function() { + function () { handlebarsEnv.parse('\n {{#foo}}{{/bar}}'); }, Handlebars.Exception, @@ -17,14 +17,14 @@ describe('ast', function() { }); }); - describe('helpers', function() { - describe('#helperExpression', function() { - it('should handle mustache statements', function() { + describe('helpers', function () { + describe('#helperExpression', function () { + it('should handle mustache statements', function () { equals( AST.helpers.helperExpression({ type: 'MustacheStatement', params: [], - hash: undefined + hash: undefined, }), false ); @@ -32,7 +32,7 @@ describe('ast', function() { AST.helpers.helperExpression({ type: 'MustacheStatement', params: [1], - hash: undefined + hash: undefined, }), true ); @@ -40,17 +40,17 @@ describe('ast', function() { AST.helpers.helperExpression({ type: 'MustacheStatement', params: [], - hash: {} + hash: {}, }), true ); }); - it('should handle block statements', function() { + it('should handle block statements', function () { equals( AST.helpers.helperExpression({ type: 'BlockStatement', params: [], - hash: undefined + hash: undefined, }), false ); @@ -58,7 +58,7 @@ describe('ast', function() { AST.helpers.helperExpression({ type: 'BlockStatement', params: [1], - hash: undefined + hash: undefined, }), true ); @@ -66,15 +66,15 @@ describe('ast', function() { AST.helpers.helperExpression({ type: 'BlockStatement', params: [], - hash: {} + hash: {}, }), true ); }); - it('should handle subexpressions', function() { + it('should handle subexpressions', function () { equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true); }); - it('should work with non-helper nodes', function() { + it('should work with non-helper nodes', function () { equals(AST.helpers.helperExpression({ type: 'Program' }), false); equals( @@ -107,7 +107,7 @@ describe('ast', function() { }); }); - describe('Line Numbers', function() { + describe('Line Numbers', function () { var ast, body; function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) { @@ -120,59 +120,59 @@ describe('ast', function() { /* eslint-disable no-multi-spaces */ ast = Handlebars.parse( 'line 1 {{line1Token}}\n' + // 1 - ' line 2 {{line2token}}\n' + // 2 - ' line 3 {{#blockHelperOnLine3}}\n' + // 3 - 'line 4{{line4token}}\n' + // 4 - 'line5{{else}}\n' + // 5 - '{{line6Token}}\n' + // 6 - '{{/blockHelperOnLine3}}\n' + // 7 - '{{#open}}\n' + // 8 - '{{else inverse}}\n' + // 9 - '{{else}}\n' + // 10 + ' line 2 {{line2token}}\n' + // 2 + ' line 3 {{#blockHelperOnLine3}}\n' + // 3 + 'line 4{{line4token}}\n' + // 4 + 'line5{{else}}\n' + // 5 + '{{line6Token}}\n' + // 6 + '{{/blockHelperOnLine3}}\n' + // 7 + '{{#open}}\n' + // 8 + '{{else inverse}}\n' + // 9 + '{{else}}\n' + // 10 '{{/open}}' ); // 11 /* eslint-enable no-multi-spaces */ body = ast.body; - it('gets ContentNode line numbers', function() { + it('gets ContentNode line numbers', function () { var contentNode = body[0]; testColumns(contentNode, 1, 1, 0, 7); }); - it('gets MustacheStatement line numbers', function() { + it('gets MustacheStatement line numbers', function () { var mustacheNode = body[1]; testColumns(mustacheNode, 1, 1, 7, 21); }); - it('gets line numbers correct when newlines appear', function() { + it('gets line numbers correct when newlines appear', function () { testColumns(body[2], 1, 2, 21, 8); }); - it('gets MustacheStatement line numbers correct across newlines', function() { + it('gets MustacheStatement line numbers correct across newlines', function () { var secondMustacheStatement = body[3]; testColumns(secondMustacheStatement, 2, 2, 8, 22); }); - it('gets the block helper information correct', function() { + it('gets the block helper information correct', function () { var blockHelperNode = body[5]; testColumns(blockHelperNode, 3, 7, 8, 23); }); - it('correctly records the line numbers the program of a block helper', function() { + it('correctly records the line numbers the program of a block helper', function () { var blockHelperNode = body[5], program = blockHelperNode.program; testColumns(program, 3, 5, 31, 5); }); - it('correctly records the line numbers of an inverse of a block helper', function() { + it('correctly records the line numbers of an inverse of a block helper', function () { var blockHelperNode = body[5], inverse = blockHelperNode.inverse; testColumns(inverse, 5, 7, 13, 0); }); - it('correctly records the line number of chained inverses', function() { + it('correctly records the line number of chained inverses', function () { var chainInverseNode = body[7]; testColumns(chainInverseNode.program, 8, 9, 9, 0); @@ -181,251 +181,4 @@ describe('ast', function() { testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0); }); }); - - describe('whitespace control', function() { - describe('parse', function() { - it('mustache', function() { - var ast = Handlebars.parse(' {{~comment~}} '); - - equals(ast.body[0].value, ''); - equals(ast.body[2].value, ''); - }); - - it('block statements', function() { - var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}'); - - equals(ast.body[0].value, ''); - equals(ast.body[1].program.body[0].value, 'foo'); - }); - }); - - describe('parseWithoutProcessing', function() { - it('mustache', function() { - var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} '); - - equals(ast.body[0].value, ' '); - equals(ast.body[2].value, ' '); - }); - - it('block statements', function() { - var ast = Handlebars.parseWithoutProcessing( - ' {{# comment~}} \nfoo\n {{~/comment}}' - ); - - equals(ast.body[0].value, ' '); - equals(ast.body[1].program.body[0].value, ' \nfoo\n '); - }); - }); - }); - - describe('standalone flags', function() { - describe('mustache', function() { - it('does not mark mustaches as standalone', function() { - var ast = Handlebars.parse(' {{comment}} '); - equals(!!ast.body[0].value, true); - equals(!!ast.body[2].value, true); - }); - }); - describe('blocks - parseWithoutProcessing', function() { - it('block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].value, ' '); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - - equals(ast.body[2].value, ' '); - }); - it('initial block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{# comment}} \nfoo\n {{/comment}}' - ), - block = ast.body[0]; - - equals(block.program.body[0].value, ' \nfoo\n '); - }); - it('mustaches with children', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{# comment}} \n{{foo}}\n {{/comment}}' - ), - block = ast.body[0]; - - equals(block.program.body[0].value, ' \n'); - equals(block.program.body[1].path.original, 'foo'); - equals(block.program.body[2].value, '\n '); - }); - it('nested block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].value, ' \n'); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - }); - it('column 0 block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].omit, undefined); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - - equals(ast.body[2].value, ' '); - }); - }); - describe('blocks', function() { - it('marks block mustaches as standalone', function() { - var ast = Handlebars.parse( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].value, ''); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(ast.body[2].value, ''); - }); - it('marks initial block mustaches as standalone', function() { - var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'), - block = ast.body[0]; - - equals(block.program.body[0].value, 'foo\n'); - }); - it('marks mustaches with children as standalone', function() { - var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'), - block = ast.body[0]; - - equals(block.program.body[0].value, ''); - equals(block.program.body[1].path.original, 'foo'); - equals(block.program.body[2].value, '\n'); - }); - it('marks nested block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].value, ''); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(body[0].value, ''); - }); - it('does not mark nested block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].omit, undefined); - - equals(block.program.body[0].value, ' \nfoo\n'); - equals(block.inverse.body[0].value, ' bar \n '); - - equals(body[0].omit, undefined); - }); - it('does not mark nested initial block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[0]; - - equals(block.program.body[0].value, ' \nfoo\n'); - equals(block.inverse.body[0].value, ' bar \n '); - - equals(body[0].omit, undefined); - }); - - it('marks column 0 block mustaches as standalone', function() { - var ast = Handlebars.parse( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].omit, undefined); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(ast.body[2].value, ''); - }); - }); - describe('partials - parseWithoutProcessing', function() { - it('simple partial', function() { - var ast = Handlebars.parseWithoutProcessing('{{> partial }} '); - equals(ast.body[1].value, ' '); - }); - it('indented partial', function() { - var ast = Handlebars.parseWithoutProcessing(' {{> partial }} '); - equals(ast.body[0].value, ' '); - equals(ast.body[1].indent, ''); - equals(ast.body[2].value, ' '); - }); - }); - describe('partials', function() { - it('marks partial as standalone', function() { - var ast = Handlebars.parse('{{> partial }} '); - equals(ast.body[1].value, ''); - }); - it('marks indented partial as standalone', function() { - var ast = Handlebars.parse(' {{> partial }} '); - equals(ast.body[0].value, ''); - equals(ast.body[1].indent, ' '); - equals(ast.body[2].value, ''); - }); - it('marks those around content as not standalone', function() { - var ast = Handlebars.parse('a{{> partial }}'); - equals(ast.body[0].omit, undefined); - - ast = Handlebars.parse('{{> partial }}a'); - equals(ast.body[1].omit, undefined); - }); - }); - describe('comments - parseWithoutProcessing', function() { - it('simple comment', function() { - var ast = Handlebars.parseWithoutProcessing('{{! comment }} '); - equals(ast.body[1].value, ' '); - }); - it('indented comment', function() { - var ast = Handlebars.parseWithoutProcessing(' {{! comment }} '); - equals(ast.body[0].value, ' '); - equals(ast.body[2].value, ' '); - }); - }); - describe('comments', function() { - it('marks comment as standalone', function() { - var ast = Handlebars.parse('{{! comment }} '); - equals(ast.body[1].value, ''); - }); - it('marks indented comment as standalone', function() { - var ast = Handlebars.parse(' {{! comment }} '); - equals(ast.body[0].value, ''); - equals(ast.body[2].value, ''); - }); - it('marks those around content as not standalone', function() { - var ast = Handlebars.parse('a{{! comment }}'); - equals(ast.body[0].omit, undefined); - - ast = Handlebars.parse('{{! comment }}a'); - equals(ast.body[1].omit, undefined); - }); - }); - }); }); diff --git a/spec/basic.js b/spec/basic.js index 4c7afb706..b7f4f637f 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -1,17 +1,15 @@ global.handlebarsEnv = null; -beforeEach(function() { +beforeEach(function () { global.handlebarsEnv = Handlebars.create(); }); -describe('basic context', function() { - it('most basic', function() { - expectTemplate('{{foo}}') - .withInput({ foo: 'foo' }) - .toCompileTo('foo'); +describe('basic context', function () { + it('most basic', function () { + expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); }); - it('escaping', function() { + it('escaping', function () { expectTemplate('\\{{foo}}') .withInput({ foo: 'food' }) .toCompileTo('{{foo}}'); @@ -33,23 +31,21 @@ describe('basic context', function() { .toCompileTo('\\\\ food'); }); - it('compiling with a basic context', function() { + it('compiling with a basic context', function () { expectTemplate('Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', - world: 'world' + world: 'world', }) .withMessage('It works if all the required keys are provided') .toCompileTo('Goodbye\ncruel\nworld!'); }); - it('compiling with a string context', function() { - expectTemplate('{{.}}{{length}}') - .withInput('bye') - .toCompileTo('bye3'); + it('compiling with a string context', function () { + expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3'); }); - it('compiling with an undefined context', function() { + it('compiling with an undefined context', function () { expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!') .withInput(undefined) .toCompileTo('Goodbye\n\n!'); @@ -59,11 +55,11 @@ describe('basic context', function() { .toCompileTo('Goodbye'); }); - it('comments', function() { + it('comments', function () { expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!') .withInput({ cruel: 'cruel', - world: 'world' + world: 'world', }) .withMessage('comments are ignored') .toCompileTo('Goodbye\ncruel\nworld!'); @@ -87,12 +83,12 @@ describe('basic context', function() { ); }); - it('boolean', function() { + it('boolean', function () { var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; expectTemplate(string) .withInput({ goodbye: true, - world: 'world' + world: 'world', }) .withMessage('booleans show the contents when true') .toCompileTo('GOODBYE cruel world!'); @@ -100,41 +96,37 @@ describe('basic context', function() { expectTemplate(string) .withInput({ goodbye: false, - world: 'world' + world: 'world', }) .withMessage('booleans do not show the contents when false') .toCompileTo('cruel world!'); }); - it('zeros', function() { + it('zeros', function () { expectTemplate('num1: {{num1}}, num2: {{num2}}') .withInput({ num1: 42, - num2: 0 + num2: 0, }) .toCompileTo('num1: 42, num2: 0'); - expectTemplate('num: {{.}}') - .withInput(0) - .toCompileTo('num: 0'); + expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0'); expectTemplate('num: {{num1/num2}}') .withInput({ num1: { num2: 0 } }) .toCompileTo('num: 0'); }); - it('false', function() { + it('false', function () { /* eslint-disable no-new-wrappers */ expectTemplate('val1: {{val1}}, val2: {{val2}}') .withInput({ val1: false, - val2: new Boolean(false) + val2: new Boolean(false), }) .toCompileTo('val1: false, val2: false'); - expectTemplate('val: {{.}}') - .withInput(false) - .toCompileTo('val: false'); + expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false'); expectTemplate('val: {{val1/val2}}') .withInput({ val1: { val2: false } }) @@ -143,7 +135,7 @@ describe('basic context', function() { expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}') .withInput({ val1: false, - val2: new Boolean(false) + val2: new Boolean(false), }) .toCompileTo('val1: false, val2: false'); @@ -153,10 +145,10 @@ describe('basic context', function() { /* eslint-enable */ }); - it('should handle undefined and null', function() { + it('should handle undefined and null', function () { expectTemplate('{{awesome undefined null}}') .withInput({ - awesome: function(_undefined, _null, options) { + awesome: function (_undefined, _null, options) { return ( (_undefined === undefined) + ' ' + @@ -164,34 +156,34 @@ describe('basic context', function() { ' ' + typeof options ); - } + }, }) .toCompileTo('true true object'); expectTemplate('{{undefined}}') .withInput({ - undefined: function() { + undefined: function () { return 'undefined!'; - } + }, }) .toCompileTo('undefined!'); expectTemplate('{{null}}') .withInput({ - null: function() { + null: function () { return 'null!'; - } + }, }) .toCompileTo('null!'); }); - it('newlines', function() { + it('newlines', function () { expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest"); expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest"); }); - it('escaping text', function() { + it('escaping text', function () { expectTemplate("Awesome's") .withMessage( "text is escaped so that it doesn't get caught on single quotes" @@ -216,7 +208,7 @@ describe('basic context', function() { .toCompileTo(" ' ' "); }); - it('escaping expressions', function() { + it('escaping expressions', function () { expectTemplate('{{{awesome}}}') .withInput({ awesome: "&'\\<>" }) .withMessage("expressions with 3 handlebars aren't escaped") @@ -238,140 +230,140 @@ describe('basic context', function() { .toCompileTo('Escaped, <b> looks like: &lt;b&gt;'); }); - it("functions returning safestrings shouldn't be escaped", function() { + it("functions returning safestrings shouldn't be escaped", function () { expectTemplate('{{awesome}}') .withInput({ - awesome: function() { + awesome: function () { return new Handlebars.SafeString("&'\\<>"); - } + }, }) .withMessage("functions returning safestrings aren't escaped") .toCompileTo("&'\\<>"); }); - it('functions', function() { + it('functions', function () { expectTemplate('{{awesome}}') .withInput({ - awesome: function() { + awesome: function () { return 'Awesome'; - } + }, }) .withMessage('functions are called and render their output') .toCompileTo('Awesome'); expectTemplate('{{awesome}}') .withInput({ - awesome: function() { + awesome: function () { return this.more; }, - more: 'More awesome' + more: 'More awesome', }) .withMessage('functions are bound to the context') .toCompileTo('More awesome'); }); - it('functions with context argument', function() { + it('functions with context argument', function () { expectTemplate('{{awesome frank}}') .withInput({ - awesome: function(context) { + awesome: function (context) { return context; }, - frank: 'Frank' + frank: 'Frank', }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); - it('pathed functions with context argument', function() { + it('pathed functions with context argument', function () { expectTemplate('{{bar.awesome frank}}') .withInput({ bar: { - awesome: function(context) { + awesome: function (context) { return context; - } + }, }, - frank: 'Frank' + frank: 'Frank', }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); - it('depthed functions with context argument', function() { + it('depthed functions with context argument', function () { expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}') .withInput({ - awesome: function(context) { + awesome: function (context) { return context; }, - frank: 'Frank' + frank: 'Frank', }) .withMessage('functions are called with context arguments') .toCompileTo('Frank'); }); - it('block functions with context argument', function() { + it('block functions with context argument', function () { expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}') .withInput({ - awesome: function(context, options) { + awesome: function (context, options) { return options.fn(context); - } + }, }) .withMessage('block functions are called with context and options') .toCompileTo('inner 1'); }); - it('depthed block functions with context argument', function() { + it('depthed block functions with context argument', function () { expectTemplate( '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' ) .withInput({ value: true, - awesome: function(context, options) { + awesome: function (context, options) { return options.fn(context); - } + }, }) .withMessage('block functions are called with context and options') .toCompileTo('inner 1'); }); - it('block functions without context argument', function() { + it('block functions without context argument', function () { expectTemplate('{{#awesome}}inner{{/awesome}}') .withInput({ - awesome: function(options) { + awesome: function (options) { return options.fn(this); - } + }, }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); - it('pathed block functions without context argument', function() { + it('pathed block functions without context argument', function () { expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}') .withInput({ foo: { - awesome: function() { + awesome: function () { return this; - } - } + }, + }, }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); - it('depthed block functions without context argument', function() { + it('depthed block functions without context argument', function () { expectTemplate( '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' ) .withInput({ value: true, - awesome: function() { + awesome: function () { return this; - } + }, }) .withMessage('block functions are called with options') .toCompileTo('inner'); }); - it('paths with hyphens', function() { + it('paths with hyphens', function () { expectTemplate('{{foo-bar}}') .withInput({ 'foo-bar': 'baz' }) .withMessage('Paths can contain hyphens (-)') @@ -388,21 +380,21 @@ describe('basic context', function() { .toCompileTo('baz'); }); - it('nested paths', function() { + it('nested paths', function () { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: 'beautiful' } }) .withMessage('Nested paths access nested objects') .toCompileTo('Goodbye beautiful world!'); }); - it('nested paths with empty string value', function() { + it('nested paths with empty string value', function () { expectTemplate('Goodbye {{alan/expression}} world!') .withInput({ alan: { expression: '' } }) .withMessage('Nested paths access nested objects with empty string') .toCompileTo('Goodbye world!'); }); - it('literal paths', function() { + it('literal paths', function () { expectTemplate('Goodbye {{[@alan]/expression}} world!') .withInput({ '@alan': { expression: 'beautiful' } }) .withMessage('Literal paths can be used') @@ -414,7 +406,7 @@ describe('basic context', function() { .toCompileTo('Goodbye beautiful world!'); }); - it('literal references', function() { + it('literal references', function () { expectTemplate('Goodbye {{[foo bar]}} world!') .withInput({ 'foo bar': 'beautiful' }) .toCompileTo('Goodbye beautiful world!'); @@ -440,24 +432,22 @@ describe('basic context', function() { .toCompileTo('Goodbye beautiful world!'); }); - it("that current context path ({{.}}) doesn't hit helpers", function() { + it("that current context path ({{.}}) doesn't hit helpers", function () { expectTemplate('test: {{.}}') .withInput(null) .withHelpers({ helper: 'awesome' }) .toCompileTo('test: '); }); - it('complex but empty paths', function() { + it('complex but empty paths', function () { expectTemplate('{{person/name}}') .withInput({ person: { name: null } }) .toCompileTo(''); - expectTemplate('{{person/name}}') - .withInput({ person: {} }) - .toCompileTo(''); + expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo(''); }); - it('this keyword in paths', function() { + it('this keyword in paths', function () { expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}') .withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] }) .withMessage('This keyword in paths evaluates to current context') @@ -465,32 +455,30 @@ describe('basic context', function() { expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}') .withInput({ - hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], }) .withMessage('This keyword evaluates in more complex paths') .toCompileTo('helloHelloHELLO'); }); - it('this keyword nested inside path', function() { + it('this keyword nested inside path', function () { expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:13' ); - expectTemplate('{{[this]}}') - .withInput({ this: 'bar' }) - .toCompileTo('bar'); + expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar'); expectTemplate('{{text/[this]}}') .withInput({ text: { this: 'bar' } }) .toCompileTo('bar'); }); - it('this keyword in helpers', function() { + it('this keyword in helpers', function () { var helpers = { - foo: function(value) { + foo: function (value) { return 'bar ' + value; - } + }, }; expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}') @@ -501,14 +489,14 @@ describe('basic context', function() { expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}') .withInput({ - hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] + hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], }) .withHelpers(helpers) .withMessage('This keyword evaluates in more complex paths') .toCompileTo('bar hellobar Hellobar HELLO'); }); - it('this keyword nested inside helpers param', function() { + it('this keyword nested inside helpers param', function () { expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow( Error, 'Invalid path: text/this - 1:17' @@ -516,79 +504,69 @@ describe('basic context', function() { expectTemplate('{{foo [this]}}') .withInput({ - foo: function(value) { + foo: function (value) { return value; }, - this: 'bar' + this: 'bar', }) .toCompileTo('bar'); expectTemplate('{{foo text/[this]}}') .withInput({ - foo: function(value) { + foo: function (value) { return value; }, - text: { this: 'bar' } + text: { this: 'bar' }, }) .toCompileTo('bar'); }); - it('pass string literals', function() { + it('pass string literals', function () { expectTemplate('{{"foo"}}').toCompileTo(''); - expectTemplate('{{"foo"}}') - .withInput({ foo: 'bar' }) - .toCompileTo('bar'); + expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar'); expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}') .withInput({ - foo: ['bar', 'baz'] + foo: ['bar', 'baz'], }) .toCompileTo('barbaz'); }); - it('pass number literals', function() { + it('pass number literals', function () { expectTemplate('{{12}}').toCompileTo(''); - expectTemplate('{{12}}') - .withInput({ '12': 'bar' }) - .toCompileTo('bar'); + expectTemplate('{{12}}').withInput({ 12: 'bar' }).toCompileTo('bar'); expectTemplate('{{12.34}}').toCompileTo(''); - expectTemplate('{{12.34}}') - .withInput({ '12.34': 'bar' }) - .toCompileTo('bar'); + expectTemplate('{{12.34}}').withInput({ 12.34: 'bar' }).toCompileTo('bar'); expectTemplate('{{12.34 1}}') .withInput({ - '12.34': function(arg) { + 12.34: function (arg) { return 'bar' + arg; - } + }, }) .toCompileTo('bar1'); }); - it('pass boolean literals', function() { + it('pass boolean literals', function () { expectTemplate('{{true}}').toCompileTo(''); - expectTemplate('{{true}}') - .withInput({ '': 'foo' }) - .toCompileTo(''); + expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo(''); - expectTemplate('{{false}}') - .withInput({ false: 'foo' }) - .toCompileTo('foo'); + expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo'); }); - it('should handle literals in subexpression', function() { + it('should handle literals in subexpression', function () { expectTemplate('{{foo (false)}}') .withInput({ - false: function() { + false: function () { return 'bar'; - } + }, }) - .withHelper('foo', function(arg) { + .withHelper('foo', function (arg) { return arg; }) .toCompileTo('bar'); diff --git a/spec/blocks.js b/spec/blocks.js index f15655428..550e64c23 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -1,5 +1,5 @@ -describe('blocks', function() { - it('array', function() { +describe('blocks', function () { + it('array', function () { var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; expectTemplate(string) @@ -7,9 +7,9 @@ describe('blocks', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('Arrays iterate over the contents when not empty') .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); @@ -17,13 +17,13 @@ describe('blocks', function() { expectTemplate(string) .withInput({ goodbyes: [], - world: 'world' + world: 'world', }) .withMessage('Arrays ignore the contents when empty') .toCompileTo('cruel world!'); }); - it('array without data', function() { + it('array without data', function () { expectTemplate( '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}' ) @@ -31,15 +31,15 @@ describe('blocks', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withCompileOptions({ compat: false }) .toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE'); }); - it('array with @index', function() { + it('array with @index', function () { expectTemplate( '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!' ) @@ -47,15 +47,15 @@ describe('blocks', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); - it('empty block', function() { + it('empty block', function () { var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; expectTemplate(string) @@ -63,9 +63,9 @@ describe('blocks', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('Arrays iterate over the contents when not empty') .toCompileTo('cruel world!'); @@ -73,21 +73,21 @@ describe('blocks', function() { expectTemplate(string) .withInput({ goodbyes: [], - world: 'world' + world: 'world', }) .withMessage('Arrays ignore the contents when empty') .toCompileTo('cruel world!'); }); - it('block with complex lookup', function() { + it('block with complex lookup', function () { expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}') .withInput({ name: 'Alan', goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } - ] + { text: 'GOODBYE' }, + ], }) .withMessage( 'Templates can access variables in contexts up the stack with relative path syntax' @@ -97,37 +97,37 @@ describe('blocks', function() { ); }); - it('multiple blocks with complex lookup', function() { + it('multiple blocks with complex lookup', function () { expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}') .withInput({ name: 'Alan', goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } - ] + { text: 'GOODBYE' }, + ], }) .toCompileTo('AlanAlanAlanAlanAlanAlan'); }); - it('block with complex lookup using nested context', function() { + it('block with complex lookup using nested context', function () { expectTemplate( '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}' ).toThrow(Error); }); - it('block with deep nested complex lookup', function() { + it('block with deep nested complex lookup', function () { expectTemplate( '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}' ) .withInput({ omg: 'OMG!', - outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] + outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }], }) .toCompileTo('Goodbye cruel sad OMG!'); }); - it('works with cached blocks', function() { + it('works with cached blocks', function () { expectTemplate( '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}' ) @@ -135,14 +135,14 @@ describe('blocks', function() { .withInput({ person: [ { first: 'Alan', last: 'Johnson' }, - { first: 'Alan', last: 'Johnson' } - ] + { first: 'Alan', last: 'Johnson' }, + ], }) .toCompileTo('Alan JohnsonAlan Johnson'); }); - describe('inverted sections', function() { - it('inverted sections with unset value', function() { + describe('inverted sections', function () { + it('inverted sections with unset value', function () { expectTemplate( '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' ) @@ -150,7 +150,7 @@ describe('blocks', function() { .toCompileTo('Right On!'); }); - it('inverted section with false value', function() { + it('inverted section with false value', function () { expectTemplate( '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' ) @@ -159,7 +159,7 @@ describe('blocks', function() { .toCompileTo('Right On!'); }); - it('inverted section with empty set', function() { + it('inverted section with empty set', function () { expectTemplate( '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' ) @@ -168,13 +168,13 @@ describe('blocks', function() { .toCompileTo('Right On!'); }); - it('block inverted sections', function() { + it('block inverted sections', function () { expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') .withInput({ none: 'No people' }) .toCompileTo('No people'); }); - it('chained inverted sections', function() { + it('chained inverted sections', function () { expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}') .withInput({ none: 'No people' }) .toCompileTo('No people'); @@ -192,24 +192,24 @@ describe('blocks', function() { .toCompileTo('No people'); }); - it('chained inverted sections with mismatch', function() { + it('chained inverted sections with mismatch', function () { expectTemplate( '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' ).toThrow(Error); }); - it('block inverted sections with empty arrays', function() { + it('block inverted sections with empty arrays', function () { expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}') .withInput({ none: 'No people', - people: [] + people: [], }) .toCompileTo('No people'); }); }); - describe('standalone sections', function() { - it('block standalone else sections', function() { + describe('standalone sections', function () { + it('block standalone else sections', function () { expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') .withInput({ none: 'No people' }) .toCompileTo('No people\n'); @@ -223,7 +223,7 @@ describe('blocks', function() { .toCompileTo('No people\n'); }); - it('block standalone else sections can be disabled', function() { + it('block standalone else sections can be disabled', function () { expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') .withInput({ none: 'No people' }) .withCompileOptions({ ignoreStandalone: true }) @@ -235,7 +235,7 @@ describe('blocks', function() { .toCompileTo('\nNo people\n\n'); }); - it('block standalone chained else sections', function() { + it('block standalone chained else sections', function () { expectTemplate( '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n' ) @@ -249,17 +249,17 @@ describe('blocks', function() { .toCompileTo('No people\n'); }); - it('should handle nesting', function() { + it('should handle nesting', function () { expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.') .withInput({ - data: [1, 3, 5] + data: [1, 3, 5], }) .toCompileTo('1\n3\n5\nOK.'); }); }); - describe('compat mode', function() { - it('block with deep recursive lookup lookup', function() { + describe('compat mode', function () { + it('block with deep recursive lookup lookup', function () { expectTemplate( '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}' ) @@ -268,108 +268,108 @@ describe('blocks', function() { .toCompileTo('Goodbye cruel OMG!'); }); - it('block with deep recursive pathed lookup', function() { + it('block with deep recursive pathed lookup', function () { expectTemplate( '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' ) .withInput({ omg: { yes: 'OMG!' }, - outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }], }) .withCompileOptions({ compat: true }) .toCompileTo('Goodbye cruel OMG!'); }); - it('block with missed recursive lookup', function() { + it('block with missed recursive lookup', function () { expectTemplate( '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' ) .withInput({ omg: { no: 'OMG!' }, - outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] + outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }], }) .withCompileOptions({ compat: true }) .toCompileTo('Goodbye cruel '); }); }); - describe('decorators', function() { - it('should apply mustache decorators', function() { + describe('decorators', function () { + it('should apply mustache decorators', function () { expectTemplate('{{#helper}}{{*decorator}}{{/helper}}') - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn.run; }) - .withDecorator('decorator', function(fn) { + .withDecorator('decorator', function (fn) { fn.run = 'success'; return fn; }) .toCompileTo('success'); }); - it('should apply allow undefined return', function() { + it('should apply allow undefined return', function () { expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}') - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn() + options.fn.run; }) - .withDecorator('decorator', function(fn) { + .withDecorator('decorator', function (fn) { fn.run = 'cess'; }) .toCompileTo('success'); }); - it('should apply block decorators', function() { + it('should apply block decorators', function () { expectTemplate( '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}' ) - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn.run; }) - .withDecorator('decorator', function(fn, props, container, options) { + .withDecorator('decorator', function (fn, props, container, options) { fn.run = options.fn(); return fn; }) .toCompileTo('success'); }); - it('should support nested decorators', function() { + it('should support nested decorators', function () { expectTemplate( '{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}' ) - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn.run; }) .withDecorators({ - decorator: function(fn, props, container, options) { + decorator: function (fn, props, container, options) { fn.run = options.fn.nested + options.fn(); return fn; }, - nested: function(fn, props, container, options) { + nested: function (fn, props, container, options) { props.nested = options.fn(); - } + }, }) .toCompileTo('success'); }); - it('should apply multiple decorators', function() { + it('should apply multiple decorators', function () { expectTemplate( '{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}' ) - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn.run; }) - .withDecorator('decorator', function(fn, props, container, options) { + .withDecorator('decorator', function (fn, props, container, options) { fn.run = (fn.run || '') + options.fn(); return fn; }) .toCompileTo('success'); }); - it('should access parent variables', function() { + it('should access parent variables', function () { expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}') - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { return options.fn.run; }) - .withDecorator('decorator', function(fn, props, container, options) { + .withDecorator('decorator', function (fn, props, container, options) { fn.run = options.args; return fn; }) @@ -377,10 +377,10 @@ describe('blocks', function() { .toCompileTo('success'); }); - it('should work with root program', function() { + it('should work with root program', function () { var run; expectTemplate('{{*decorator "success"}}') - .withDecorator('decorator', function(fn, props, container, options) { + .withDecorator('decorator', function (fn, props, container, options) { equals(options.args[0], 'success'); run = true; return fn; @@ -390,10 +390,10 @@ describe('blocks', function() { equals(run, true); }); - it('should fail when accessing variables from root', function() { + it('should fail when accessing variables from root', function () { var run; expectTemplate('{{*decorator foo}}') - .withDecorator('decorator', function(fn, props, container, options) { + .withDecorator('decorator', function (fn, props, container, options) { equals(options.args[0], undefined); run = true; return fn; @@ -403,11 +403,11 @@ describe('blocks', function() { equals(run, true); }); - describe('registration', function() { - it('unregisters', function() { + describe('registration', function () { + it('unregisters', function () { handlebarsEnv.decorators = {}; - handlebarsEnv.registerDecorator('foo', function() { + handlebarsEnv.registerDecorator('foo', function () { return 'fail'; }); @@ -416,12 +416,12 @@ describe('blocks', function() { equals(handlebarsEnv.decorators.foo, undefined); }); - it('allows multiple globals', function() { + it('allows multiple globals', function () { handlebarsEnv.decorators = {}; handlebarsEnv.registerDecorator({ - foo: function() {}, - bar: function() {} + foo: function () {}, + bar: function () {}, }); equals(!!handlebarsEnv.decorators.foo, true); @@ -432,17 +432,17 @@ describe('blocks', function() { equals(handlebarsEnv.decorators.bar, undefined); }); - it('fails with multiple and args', function() { + it('fails with multiple and args', function () { shouldThrow( - function() { + function () { handlebarsEnv.registerDecorator( { - world: function() { + world: function () { return 'world!'; }, - testHelper: function() { + testHelper: function () { return 'found it!'; - } + }, }, {} ); diff --git a/spec/builtins.js b/spec/builtins.js index a43fb81f5..4c1efe4eb 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -1,12 +1,12 @@ -describe('builtin helpers', function() { - describe('#if', function() { - it('if', function() { +describe('builtin helpers', function () { + describe('#if', function () { + it('if', function () { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; expectTemplate(string) .withInput({ goodbye: true, - world: 'world' + world: 'world', }) .withMessage('if with boolean argument shows the contents when true') .toCompileTo('GOODBYE cruel world!'); @@ -14,7 +14,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbye: 'dummy', - world: 'world' + world: 'world', }) .withMessage('if with string argument shows the contents') .toCompileTo('GOODBYE cruel world!'); @@ -22,7 +22,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbye: false, - world: 'world' + world: 'world', }) .withMessage( 'if with boolean argument does not show the contents when false' @@ -37,7 +37,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbye: ['foo'], - world: 'world' + world: 'world', }) .withMessage('if with non-empty array shows the contents') .toCompileTo('GOODBYE cruel world!'); @@ -45,7 +45,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbye: [], - world: 'world' + world: 'world', }) .withMessage('if with empty array does not show the contents') .toCompileTo('cruel world!'); @@ -53,7 +53,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbye: 0, - world: 'world' + world: 'world', }) .withMessage('if with zero does not show the contents') .toCompileTo('cruel world!'); @@ -63,21 +63,21 @@ describe('builtin helpers', function() { ) .withInput({ goodbye: 0, - world: 'world' + world: 'world', }) .withMessage('if with zero does not show the contents') .toCompileTo('GOODBYE cruel world!'); }); - it('if with function argument', function() { + it('if with function argument', function () { var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; expectTemplate(string) .withInput({ - goodbye: function() { + goodbye: function () { return true; }, - world: 'world' + world: 'world', }) .withMessage( 'if with function shows the contents when function returns true' @@ -86,10 +86,10 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ - goodbye: function() { + goodbye: function () { return this.world; }, - world: 'world' + world: 'world', }) .withMessage( 'if with function shows the contents when function returns string' @@ -98,10 +98,10 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ - goodbye: function() { + goodbye: function () { return false; }, - world: 'world' + world: 'world', }) .withMessage( 'if with function does not show the contents when returns false' @@ -110,10 +110,10 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ - goodbye: function() { + goodbye: function () { return this.foo; }, - world: 'world' + world: 'world', }) .withMessage( 'if with function does not show the contents when returns undefined' @@ -121,61 +121,61 @@ describe('builtin helpers', function() { .toCompileTo('cruel world!'); }); - it('should not change the depth list', function() { + it('should not change the depth list', function () { expectTemplate( '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' ) .withInput({ foo: { goodbye: true }, - world: 'world' + world: 'world', }) .toCompileTo('GOODBYE cruel world!'); }); }); - describe('#with', function() { - it('with', function() { + describe('#with', function () { + it('with', function () { expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', - last: 'Johnson' - } + last: 'Johnson', + }, }) .toCompileTo('Alan Johnson'); }); - it('with with function argument', function() { + it('with with function argument', function () { expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}') .withInput({ - person: function() { + person: function () { return { first: 'Alan', - last: 'Johnson' + last: 'Johnson', }; - } + }, }) .toCompileTo('Alan Johnson'); }); - it('with with else', function() { + it('with with else', function () { expectTemplate( '{{#with person}}Person is present{{else}}Person is not present{{/with}}' ).toCompileTo('Person is not present'); }); - it('with provides block parameter', function() { + it('with provides block parameter', function () { expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', - last: 'Johnson' - } + last: 'Johnson', + }, }) .toCompileTo('Alan Johnson'); }); - it('works when data is disabled', function() { + it('works when data is disabled', function () { expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}') .withInput({ person: { first: 'Alan', last: 'Johnson' } }) .withCompileOptions({ data: false }) @@ -183,14 +183,14 @@ describe('builtin helpers', function() { }); }); - describe('#each', function() { - beforeEach(function() { - handlebarsEnv.registerHelper('detectDataInsideEach', function(options) { + describe('#each', function () { + beforeEach(function () { + handlebarsEnv.registerHelper('detectDataInsideEach', function (options) { return options.data && options.data.exclaim; }); }); - it('each', function() { + it('each', function () { var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; expectTemplate(string) @@ -198,9 +198,9 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage( 'each with array argument iterates over the contents when not empty' @@ -210,21 +210,21 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbyes: [], - world: 'world' + world: 'world', }) .withMessage('each with array argument ignores the contents when empty') .toCompileTo('cruel world!'); }); - it('each without data', function() { + it('each without data', function () { expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') .withInput({ goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withRuntimeOptions({ data: false }) .withCompileOptions({ data: false }) @@ -237,13 +237,13 @@ describe('builtin helpers', function() { .toCompileTo('cruelworld'); }); - it('each without context', function() { + it('each without context', function () { expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!') .withInput(undefined) .toCompileTo('cruel !'); }); - it('each with an object and @key', function() { + it('each with an object and @key', function () { var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; @@ -272,12 +272,12 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbyes: {}, - world: 'world' + world: 'world', }) .toCompileTo('cruel world!'); }); - it('each with @index', function() { + it('each with @index', function () { expectTemplate( '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' ) @@ -285,15 +285,15 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); - it('each with nested @index', function() { + it('each with nested @index', function () { expectTemplate( '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!' ) @@ -301,9 +301,9 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @index variable is used') .toCompileTo( @@ -311,20 +311,29 @@ describe('builtin helpers', function() { ); }); - it('each with block params', function() { + it('each with block params', function () { expectTemplate( '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!' ) .withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }], - world: 'world' + world: 'world', }) .toCompileTo( '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' ); }); - it('each object with @index', function() { + it('each with block params and strict compilation', function () { + expectTemplate( + '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}' + ) + .withCompileOptions({ strict: true }) + .withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] }) + .toCompileTo('0. goodbye!1. Goodbye!'); + }); + + it('each object with @index', function () { expectTemplate( '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' ) @@ -332,15 +341,15 @@ describe('builtin helpers', function() { goodbyes: { a: { text: 'goodbye' }, b: { text: 'Goodbye' }, - c: { text: 'GOODBYE' } + c: { text: 'GOODBYE' }, }, - world: 'world' + world: 'world', }) .withMessage('The @index variable is used') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); - it('each with @first', function() { + it('each with @first', function () { expectTemplate( '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' ) @@ -348,15 +357,15 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @first variable is used') .toCompileTo('goodbye! cruel world!'); }); - it('each with nested @first', function() { + it('each with nested @first', function () { expectTemplate( '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' ) @@ -364,9 +373,9 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @first variable is used') .toCompileTo( @@ -374,19 +383,19 @@ describe('builtin helpers', function() { ); }); - it('each object with @first', function() { + it('each object with @first', function () { expectTemplate( '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' ) .withInput({ goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, - world: 'world' + world: 'world', }) .withMessage('The @first variable is used') .toCompileTo('goodbye! cruel world!'); }); - it('each with @last', function() { + it('each with @last', function () { expectTemplate( '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' ) @@ -394,27 +403,27 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @last variable is used') .toCompileTo('GOODBYE! cruel world!'); }); - it('each object with @last', function() { + it('each object with @last', function () { expectTemplate( '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' ) .withInput({ goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } }, - world: 'world' + world: 'world', }) .withMessage('The @last variable is used') .toCompileTo('Goodbye! cruel world!'); }); - it('each with nested @last', function() { + it('each with nested @last', function () { expectTemplate( '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!' ) @@ -422,9 +431,9 @@ describe('builtin helpers', function() { goodbyes: [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ], - world: 'world' + world: 'world', }) .withMessage('The @last variable is used') .toCompileTo( @@ -432,19 +441,19 @@ describe('builtin helpers', function() { ); }); - it('each with function argument', function() { + it('each with function argument', function () { var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; expectTemplate(string) .withInput({ - goodbyes: function() { + goodbyes: function () { return [ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ]; }, - world: 'world' + world: 'world', }) .withMessage( 'each with array function argument iterates over the contents when not empty' @@ -454,7 +463,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbyes: [], - world: 'world' + world: 'world', }) .withMessage( 'each with array function argument ignores the contents when empty' @@ -462,7 +471,7 @@ describe('builtin helpers', function() { .toCompileTo('cruel world!'); }); - it('each object when last key is an empty string', function() { + it('each object when last key is an empty string', function () { expectTemplate( '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' ) @@ -470,15 +479,15 @@ describe('builtin helpers', function() { goodbyes: { a: { text: 'goodbye' }, b: { text: 'Goodbye' }, - '': { text: 'GOODBYE' } + '': { text: 'GOODBYE' }, }, - world: 'world' + world: 'world', }) .withMessage('Empty string key is not skipped') .toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!'); }); - it('data passed to helpers', function() { + it('data passed to helpers', function () { expectTemplate( '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' ) @@ -486,13 +495,13 @@ describe('builtin helpers', function() { .withMessage('should output data') .withRuntimeOptions({ data: { - exclaim: '!' - } + exclaim: '!', + }, }) .toCompileTo('a!b!c!'); }); - it('each on implicit context', function() { + it('each on implicit context', function () { expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( handlebarsEnv.Exception, 'Must pass iterator to #each' @@ -500,12 +509,12 @@ describe('builtin helpers', function() { }); if (global.Symbol && global.Symbol.iterator) { - it('each on iterable', function() { + it('each on iterable', function () { function Iterator(arr) { this.arr = arr; this.index = 0; } - Iterator.prototype.next = function() { + Iterator.prototype.next = function () { var value = this.arr[this.index]; var done = this.index === this.arr.length; if (!done) { @@ -516,7 +525,7 @@ describe('builtin helpers', function() { function Iterable(arr) { this.arr = arr; } - Iterable.prototype[global.Symbol.iterator] = function() { + Iterable.prototype[global.Symbol.iterator] = function () { return new Iterator(this.arr); }; var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; @@ -526,9 +535,9 @@ describe('builtin helpers', function() { goodbyes: new Iterable([ { text: 'goodbye' }, { text: 'Goodbye' }, - { text: 'GOODBYE' } + { text: 'GOODBYE' }, ]), - world: 'world' + world: 'world', }) .withMessage( 'each with array argument iterates over the contents when not empty' @@ -538,7 +547,7 @@ describe('builtin helpers', function() { expectTemplate(string) .withInput({ goodbyes: new Iterable([]), - world: 'world' + world: 'world', }) .withMessage( 'each with array argument ignores the contents when empty' @@ -548,27 +557,27 @@ describe('builtin helpers', function() { } }); - describe('#log', function() { + describe('#log', function () { /* eslint-disable no-console */ if (typeof console === 'undefined') { return; } var $log, $info, $error; - beforeEach(function() { + beforeEach(function () { $log = console.log; $info = console.info; $error = console.error; }); - afterEach(function() { + afterEach(function () { console.log = $log; console.info = $info; console.error = $error; }); - it('should call logger at default level', function() { + it('should call logger at default level', function () { var levelArg, logArg; - handlebarsEnv.log = function(level, arg) { + handlebarsEnv.log = function (level, arg) { levelArg = level; logArg = arg; }; @@ -581,9 +590,9 @@ describe('builtin helpers', function() { equals('whee', logArg, "should call log with 'whee'"); }); - it('should call logger at data level', function() { + it('should call logger at data level', function () { var levelArg, logArg; - handlebarsEnv.log = function(level, arg) { + handlebarsEnv.log = function (level, arg) { levelArg = level; logArg = arg; }; @@ -597,16 +606,16 @@ describe('builtin helpers', function() { equals('whee', logArg); }); - it('should output to info', function() { + it('should output to info', function () { var called; - console.info = function(info) { + console.info = function (info) { equals('whee', info); called = true; console.info = $info; console.log = $log; }; - console.log = function(log) { + console.log = function (log) { equals('whee', log); called = true; console.info = $info; @@ -619,10 +628,10 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should log at data level', function() { + it('should log at data level', function () { var called; - console.error = function(log) { + console.error = function (log) { equals('whee', log); called = true; console.error = $error; @@ -636,11 +645,11 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should handle missing logger', function() { + it('should handle missing logger', function () { var called = false; console.error = undefined; - console.log = function(log) { + console.log = function (log) { equals('whee', log); called = true; console.log = $log; @@ -654,10 +663,10 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should handle string log levels', function() { + it('should handle string log levels', function () { var called; - console.error = function(log) { + console.error = function (log) { equals('whee', log); called = true; }; @@ -679,10 +688,10 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should handle hash log levels', function() { + it('should handle hash log levels', function () { var called; - console.error = function(log) { + console.error = function (log) { equals('whee', log); called = true; }; @@ -693,13 +702,17 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should handle hash log levels', function() { + it('should handle hash log levels', function () { var called = false; - console.info = console.log = console.error = console.debug = function() { - called = true; - console.info = console.log = console.error = console.debug = $log; - }; + console.info = + console.log = + console.error = + console.debug = + function () { + called = true; + console.info = console.log = console.error = console.debug = $log; + }; expectTemplate('{{log blah level="debug"}}') .withInput({ blah: 'whee' }) @@ -707,10 +720,10 @@ describe('builtin helpers', function() { equals(false, called); }); - it('should pass multiple log arguments', function() { + it('should pass multiple log arguments', function () { var called; - console.info = console.log = function(log1, log2, log3) { + console.info = console.log = function (log1, log2, log3) { equals('whee', log1); equals('foo', log2); equals(1, log3); @@ -724,31 +737,29 @@ describe('builtin helpers', function() { equals(true, called); }); - it('should pass zero log arguments', function() { + it('should pass zero log arguments', function () { var called; - console.info = console.log = function() { + console.info = console.log = function () { expect(arguments.length).to.equal(0); called = true; console.log = $log; }; - expectTemplate('{{log}}') - .withInput({ blah: 'whee' }) - .toCompileTo(''); + expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo(''); expect(called).to.be.true(); }); /* eslint-enable no-console */ }); - describe('#lookup', function() { - it('should lookup arbitrary content', function() { + describe('#lookup', function () { + it('should lookup arbitrary content', function () { expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}') .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) .toCompileTo('foobar'); }); - it('should not fail on undefined value', function() { + it('should not fail on undefined value', function () { expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}') .withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] }) .toCompileTo(''); diff --git a/spec/compiler.js b/spec/compiler.js index fe394b720..22f5f5a29 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -1,15 +1,15 @@ -describe('compiler', function() { +describe('compiler', function () { if (!Handlebars.compile) { return; } - describe('#equals', function() { + describe('#equals', function () { function compile(string) { var ast = Handlebars.parse(string); return new Handlebars.Compiler().compile(ast, {}); } - it('should treat as equal', 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); @@ -30,7 +30,7 @@ describe('compiler', function() { true ); }); - it('should treat as not equal', function() { + it('should treat as not equal', function () { equal(compile('foo').equals(compile('bar')), false); equal(compile('{{foo}}').equals(compile('{{bar}}')), false); equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false); @@ -59,17 +59,17 @@ describe('compiler', function() { }); }); - describe('#compile', function() { - it('should fail with invalid input', function() { + describe('#compile', function () { + it('should fail with invalid input', function () { shouldThrow( - function() { + function () { Handlebars.compile(null); }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null' ); shouldThrow( - function() { + function () { Handlebars.compile({}); }, Error, @@ -77,7 +77,7 @@ describe('compiler', function() { ); }); - it('should include the location in the error (row and column)', function() { + it('should include the location in the error (row and column)', function () { try { Handlebars.compile(' \n {{#if}}\n{{/def}}')(); equal( @@ -101,7 +101,7 @@ describe('compiler', function() { } }); - it('should include the location as enumerable property', function() { + it('should include the location as enumerable property', function () { try { Handlebars.compile(' \n {{#if}}\n{{/def}}')(); equal( @@ -118,21 +118,38 @@ describe('compiler', function() { } }); - it('can utilize AST instance', function() { + it('can utilize AST instance', function () { equal( Handlebars.compile({ type: 'Program', - body: [{ type: 'ContentStatement', value: 'Hello' }] + body: [{ type: 'ContentStatement', value: 'Hello' }], })(), 'Hello' ); }); - it('can pass through an empty string', function() { + it('can pass through an empty string', function () { equal(Handlebars.compile('')(), ''); }); - it('should not modify the options.data property(GH-1327)', function() { + it('throws on desupported options', function () { + shouldThrow( + function () { + Handlebars.compile('Dudes', { trackIds: true }); + }, + Error, + 'TrackIds and stringParams are no longer supported. See Github #1145' + ); + shouldThrow( + function () { + Handlebars.compile('Dudes', { stringParams: true }); + }, + Error, + 'TrackIds and stringParams are no longer supported. See Github #1145' + ); + }); + + 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( @@ -141,7 +158,7 @@ describe('compiler', function() { ); }); - it('should not modify the options.knownHelpers property(GH-1327)', function() { + it('should not modify the options.knownHelpers property(GH-1327)', function () { var options = { knownHelpers: {} }; Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); equal( @@ -151,37 +168,37 @@ describe('compiler', function() { }); }); - describe('#precompile', function() { - it('should fail with invalid input', function() { + describe('#precompile', function () { + it('should fail with invalid input', function () { shouldThrow( - function() { + function () { Handlebars.precompile(null); }, Error, - 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null' + 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null' ); shouldThrow( - function() { + function () { Handlebars.precompile({}); }, Error, - 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]' + 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]' ); }); - it('can utilize AST instance', function() { + it('can utilize AST instance', function () { equal( /return "Hello"/.test( Handlebars.precompile({ type: 'Program', - body: [{ type: 'ContentStatement', value: 'Hello' }] + body: [{ type: 'ContentStatement', value: 'Hello' }], }) ), true ); }); - it('can pass through an empty string', function() { + it('can pass through an empty string', function () { equal(/return ""/.test(Handlebars.precompile('')), true); }); }); diff --git a/spec/data.js b/spec/data.js index bde617326..5402c4f9d 100644 --- a/spec/data.js +++ b/spec/data.js @@ -1,8 +1,8 @@ -describe('data', function() { - it('passing in data to a compiled function that expects data - works with helpers', function() { +describe('data', function () { + it('passing in data to a compiled function that expects data - works with helpers', function () { expectTemplate('{{hello}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.data.adjective + ' ' + this.noun; }) .withRuntimeOptions({ data: { adjective: 'happy' } }) @@ -11,17 +11,17 @@ describe('data', function() { .toCompileTo('happy cat'); }); - it('data can be looked up via @foo', function() { + it('data can be looked up via @foo', function () { expectTemplate('{{@hello}}') .withRuntimeOptions({ data: { hello: 'hello' } }) .withMessage('@foo retrieves template data') .toCompileTo('hello'); }); - it('deep @foo triggers automatic top-level data', function() { + it('deep @foo triggers automatic top-level data', function () { var helpers = Handlebars.createFrame(handlebarsEnv.helpers); - helpers.let = function(options) { + helpers.let = function (options) { var frame = Handlebars.createFrame(options.data); for (var prop in options.hash) { @@ -41,83 +41,83 @@ describe('data', function() { .toCompileTo('Hello world'); }); - it('parameter data can be looked up via @foo', function() { + it('parameter data can be looked up via @foo', function () { expectTemplate('{{hello @world}}') .withRuntimeOptions({ data: { world: 'world' } }) - .withHelper('hello', function(noun) { + .withHelper('hello', function (noun) { return 'Hello ' + noun; }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); - it('hash values can be looked up via @foo', function() { + it('hash values can be looked up via @foo', function () { expectTemplate('{{hello noun=@world}}') .withRuntimeOptions({ data: { world: 'world' } }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return 'Hello ' + options.hash.noun; }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); - it('nested parameter data can be looked up via @foo.bar', function() { + it('nested parameter data can be looked up via @foo.bar', function () { expectTemplate('{{hello @world.bar}}') .withRuntimeOptions({ data: { world: { bar: 'world' } } }) - .withHelper('hello', function(noun) { + .withHelper('hello', function (noun) { return 'Hello ' + noun; }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello world'); }); - it('nested parameter data does not fail with @world.bar', function() { + it('nested parameter data does not fail with @world.bar', function () { expectTemplate('{{hello @world.bar}}') .withRuntimeOptions({ data: { foo: { bar: 'world' } } }) - .withHelper('hello', function(noun) { + .withHelper('hello', function (noun) { return 'Hello ' + noun; }) .withMessage('@foo as a parameter retrieves template data') .toCompileTo('Hello undefined'); }); - it('parameter data throws when using complex scope references', function() { + it('parameter data throws when using complex scope references', function () { expectTemplate( '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' ).toThrow(Error); }); - it('data can be functions', function() { + it('data can be functions', function () { expectTemplate('{{@hello}}') .withRuntimeOptions({ data: { - hello: function() { + hello: function () { return 'hello'; - } - } + }, + }, }) .toCompileTo('hello'); }); - it('data can be functions with params', function() { + it('data can be functions with params', function () { expectTemplate('{{@hello "hello"}}') .withRuntimeOptions({ data: { - hello: function(arg) { + hello: function (arg) { return arg; - } - } + }, + }, }) .toCompileTo('hello'); }); - it('data is inherited downstream', function() { + it('data is inherited downstream', function () { expectTemplate( '{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}' ) .withInput({ bar: { baz: 'hello world' } }) .withCompileOptions({ data: true }) - .withHelper('let', function(options) { + .withHelper('let', function (options) { var frame = Handlebars.createFrame(options.data); for (var prop in options.hash) { if (prop in options.hash) { @@ -131,11 +131,11 @@ describe('data', function() { .toCompileTo('2hello world1'); }); - it('passing in data to a compiled function that expects data - works with helpers in partials', function() { + it('passing in data to a compiled function that expects data - works with helpers in partials', function () { expectTemplate('{{>myPartial}}') .withCompileOptions({ data: true }) .withPartial('myPartial', '{{hello}}') - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.data.adjective + ' ' + this.noun; }) .withInput({ noun: 'cat' }) @@ -144,10 +144,10 @@ describe('data', function() { .toCompileTo('happy cat'); }); - it('passing in data to a compiled function that expects data - works with helpers and parameters', function() { + it('passing in data to a compiled function that expects data - works with helpers and parameters', function () { expectTemplate('{{hello world}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(noun, options) { + .withHelper('hello', function (noun, options) { return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : ''); }) .withInput({ exclaim: true, world: 'world' }) @@ -156,15 +156,15 @@ describe('data', function() { .toCompileTo('happy world!'); }); - it('passing in data to a compiled function that expects data - works with block helpers', function() { + it('passing in data to a compiled function that expects data - works with block helpers', function () { expectTemplate('{{#hello}}{{world}}{{/hello}}') .withCompileOptions({ - data: true + data: true, }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.fn(this); }) - .withHelper('world', function(options) { + .withHelper('world', function (options) { return options.data.adjective + ' world' + (this.exclaim ? '!' : ''); }) .withInput({ exclaim: true }) @@ -173,13 +173,13 @@ describe('data', function() { .toCompileTo('happy world!'); }); - it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() { + it('passing in data to a compiled function that expects data - works with block helpers that use ..', function () { expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.fn({ exclaim: '?' }); }) - .withHelper('world', function(thing, options) { + .withHelper('world', function (thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); }) .withInput({ exclaim: true, zomg: 'world' }) @@ -188,13 +188,13 @@ describe('data', function() { .toCompileTo('happy world?'); }); - it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() { + it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function () { expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.data.accessData + ' ' + options.fn({ exclaim: '?' }); }) - .withHelper('world', function(thing, options) { + .withHelper('world', function (thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); }) .withInput({ exclaim: true, zomg: 'world' }) @@ -203,41 +203,41 @@ describe('data', function() { .toCompileTo('#win happy world?'); }); - it('you can override inherited data when invoking a helper', function() { + it('you can override inherited data when invoking a helper', function () { expectTemplate('{{#hello}}{{world zomg}}{{/hello}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.fn( { exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } } ); }) - .withHelper('world', function(thing, options) { + .withHelper('world', function (thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); }) .withInput({ exclaim: true, zomg: 'planet' }) .withRuntimeOptions({ data: { adjective: 'happy' } }) - .withMessage('Overriden data output by helper') + .withMessage('Overridden data output by helper') .toCompileTo('sad world?'); }); - it('you can override inherited data when invoking a helper with depth', function() { + it('you can override inherited data when invoking a helper with depth', function () { expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}') .withCompileOptions({ data: true }) - .withHelper('hello', function(options) { + .withHelper('hello', function (options) { return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } }); }) - .withHelper('world', function(thing, options) { + .withHelper('world', function (thing, options) { return options.data.adjective + ' ' + thing + (this.exclaim || ''); }) .withInput({ exclaim: true, zomg: 'world' }) .withRuntimeOptions({ data: { adjective: 'happy' } }) - .withMessage('Overriden data output by helper') + .withMessage('Overridden data output by helper') .toCompileTo('sad world?'); }); - describe('@root', function() { - it('the root context can be looked up via @root', function() { + describe('@root', function () { + it('the root context can be looked up via @root', function () { expectTemplate('{{@root.foo}}') .withInput({ foo: 'hello' }) .withRuntimeOptions({ data: {} }) @@ -248,7 +248,7 @@ describe('data', function() { .toCompileTo('hello'); }); - it('passed root values take priority', function() { + it('passed root values take priority', function () { expectTemplate('{{@root.foo}}') .withInput({ foo: 'should not be used' }) .withRuntimeOptions({ data: { root: { foo: 'hello' } } }) @@ -256,21 +256,21 @@ describe('data', function() { }); }); - describe('nesting', function() { - it('the root context can be looked up via @root', function() { + describe('nesting', function () { + it('the root context can be looked up via @root', function () { expectTemplate( '{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}' ) .withInput({ foo: 'hello' }) - .withHelper('helper', function(options) { + .withHelper('helper', function (options) { var frame = Handlebars.createFrame(options.data); frame.depth = options.data.depth + 1; return options.fn(this, { data: frame }); }) .withRuntimeOptions({ data: { - depth: 0 - } + depth: 0, + }, }) .toCompileTo('2 1 0'); }); diff --git a/spec/env/browser.js b/spec/env/browser.js index 28541b776..19b21070b 100644 --- a/spec/env/browser.js +++ b/spec/env/browser.js @@ -26,13 +26,13 @@ vm.runInThisContext(distHandlebars, filename); global.CompilerContext = { browser: true, - compile: function(template, options) { + compile: function (template, options) { var templateSpec = handlebarsEnv.precompile(template, options); return handlebarsEnv.template(safeEval(templateSpec)); }, - compileWithPartial: function(template, options) { + compileWithPartial: function (template, options) { return handlebarsEnv.compile(template, options); - } + }, }; function safeEval(templateSpec) { diff --git a/spec/env/common.js b/spec/env/common.js index a122f4d6e..b32d71691 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -1,4 +1,4 @@ -var global = (function() { +var global = (function () { return this; })(); @@ -21,7 +21,7 @@ if (Error.captureStackTrace) { /** * @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead */ -global.shouldCompileTo = function(string, hashOrArray, expected, message) { +global.shouldCompileTo = function (string, hashOrArray, expected, message) { shouldCompileToWithPartials(string, hashOrArray, false, expected, message); }; @@ -47,7 +47,7 @@ global.shouldCompileToWithPartials = function shouldCompileToWithPartials( /** * @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead */ -global.compileWithPartials = function(string, hashOrArray, partials) { +global.compileWithPartials = function (string, hashOrArray, partials) { var template, ary, options; if (hashOrArray && hashOrArray.hash) { ary = [hashOrArray.hash, hashOrArray]; @@ -92,7 +92,7 @@ global.equals = global.equal = function equals(a, b, msg) { * @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) { +global.shouldThrow = function (callback, type, msg) { var failed; try { callback(); @@ -121,7 +121,7 @@ global.shouldThrow = function(callback, type, msg) { } }; -global.expectTemplate = function(templateAsString) { +global.expectTemplate = function (templateAsString) { return new HandlebarsTestBench(templateAsString); }; @@ -137,38 +137,38 @@ function HandlebarsTestBench(templateAsString) { this.runtimeOptions = {}; } -HandlebarsTestBench.prototype.withInput = function(input) { +HandlebarsTestBench.prototype.withInput = function (input) { this.input = input; return this; }; -HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) { +HandlebarsTestBench.prototype.withHelper = function (name, helperFunction) { this.helpers[name] = helperFunction; return this; }; -HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) { +HandlebarsTestBench.prototype.withHelpers = function (helperFunctions) { var self = this; - Object.keys(helperFunctions).forEach(function(name) { + Object.keys(helperFunctions).forEach(function (name) { self.withHelper(name, helperFunctions[name]); }); return this; }; -HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) { +HandlebarsTestBench.prototype.withPartial = function (name, partialAsString) { this.partials[name] = partialAsString; return this; }; -HandlebarsTestBench.prototype.withPartials = function(partials) { +HandlebarsTestBench.prototype.withPartials = function (partials) { var self = this; - Object.keys(partials).forEach(function(name) { + Object.keys(partials).forEach(function (name) { self.withPartial(name, partials[name]); }); return this; }; -HandlebarsTestBench.prototype.withDecorator = function( +HandlebarsTestBench.prototype.withDecorator = function ( name, decoratorFunction ) { @@ -176,30 +176,30 @@ HandlebarsTestBench.prototype.withDecorator = function( return this; }; -HandlebarsTestBench.prototype.withDecorators = function(decorators) { +HandlebarsTestBench.prototype.withDecorators = function (decorators) { var self = this; - Object.keys(decorators).forEach(function(name) { + Object.keys(decorators).forEach(function (name) { self.withDecorator(name, decorators[name]); }); return this; }; -HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) { +HandlebarsTestBench.prototype.withCompileOptions = function (compileOptions) { this.compileOptions = compileOptions; return this; }; -HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) { +HandlebarsTestBench.prototype.withRuntimeOptions = function (runtimeOptions) { this.runtimeOptions = runtimeOptions; return this; }; -HandlebarsTestBench.prototype.withMessage = function(message) { +HandlebarsTestBench.prototype.withMessage = function (message) { this.message = message; return this; }; -HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { +HandlebarsTestBench.prototype.toCompileTo = function (expectedOutputAsString) { expect(this._compileAndExecute()).to.equal( expectedOutputAsString, this.message @@ -207,14 +207,14 @@ HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) { }; // see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw) -HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) { +HandlebarsTestBench.prototype.toThrow = function (errorLike, errMsgMatcher) { var self = this; - expect(function() { + expect(function () { self._compileAndExecute(); }).to.throw(errorLike, errMsgMatcher, this.message); }; -HandlebarsTestBench.prototype._compileAndExecute = function() { +HandlebarsTestBench.prototype._compileAndExecute = function () { var compile = Object.keys(this.partials).length > 0 ? CompilerContext.compileWithPartial @@ -226,10 +226,10 @@ HandlebarsTestBench.prototype._compileAndExecute = function() { return template(this.input, combinedRuntimeOptions); }; -HandlebarsTestBench.prototype._combineRuntimeOptions = function() { +HandlebarsTestBench.prototype._combineRuntimeOptions = function () { var self = this; var combinedRuntimeOptions = {}; - Object.keys(this.runtimeOptions).forEach(function(key) { + Object.keys(this.runtimeOptions).forEach(function (key) { combinedRuntimeOptions[key] = self.runtimeOptions[key]; }); combinedRuntimeOptions.helpers = this.helpers; diff --git a/spec/env/node.js b/spec/env/node.js index 1d1cd09c8..bb96c1ecb 100644 --- a/spec/env/node.js +++ b/spec/env/node.js @@ -11,13 +11,13 @@ global.sinon = require('sinon'); global.Handlebars = require('../../lib'); global.CompilerContext = { - compile: function(template, options) { + compile: function (template, options) { var templateSpec = handlebarsEnv.precompile(template, options); return handlebarsEnv.template(safeEval(templateSpec)); }, - compileWithPartial: function(template, options) { + compileWithPartial: function (template, options) { return handlebarsEnv.compile(template, options); - } + }, }; function safeEval(templateSpec) { diff --git a/spec/env/runner.js b/spec/env/runner.js index 39c522cad..ffd0b8b21 100644 --- a/spec/env/runner.js +++ b/spec/env/runner.js @@ -15,25 +15,25 @@ if (grep === '--min') { var files = fs .readdirSync(testDir) - .filter(function(name) { + .filter(function (name) { return /.*\.js$/.test(name); }) - .map(function(name) { + .map(function (name) { return testDir + path.sep + name; }); if (global.minimizedTest) { - run('./runtime', function() { - run('./browser', function() { + 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() { + run('./runtime', function () { + run('./browser', function () { + run('./node', function () { /* eslint-disable no-process-exit */ process.exit(errors); /* eslint-enable no-process-exit */ @@ -50,13 +50,13 @@ function run(env, callback) { mocha.grep(grep); } - files.forEach(function(name) { + files.forEach(function (name) { delete require.cache[name]; }); console.log('Running env: ' + env); require(env); - mocha.run(function(errorCount) { + mocha.run(function (errorCount) { errors += errorCount; callback(); }); diff --git a/spec/env/runtime.js b/spec/env/runtime.js index 99b5bf785..43ea83171 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -22,16 +22,19 @@ vm.runInThisContext( filename ); -var parse = require('../../dist/cjs/handlebars/compiler/base').parse; +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) { + compile: function (template, options) { // Hack the compiler on to the environment for these specific tests - handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) { + handlebarsEnv.precompile = function ( + precompileTemplate, + precompileOptions + ) { return compiler.precompile( precompileTemplate, precompileOptions, @@ -45,9 +48,9 @@ global.CompilerContext = { var templateSpec = handlebarsEnv.precompile(template, options); return handlebarsEnv.template(safeEval(templateSpec)); }, - compileWithPartial: function(template, options) { + compileWithPartial: function (template, options) { // Hack the compiler on to the environment for these specific tests - handlebarsEnv.compile = function(compileTemplate, compileOptions) { + handlebarsEnv.compile = function (compileTemplate, compileOptions) { return compiler.compile(compileTemplate, compileOptions, handlebarsEnv); }; handlebarsEnv.parse = parse; @@ -55,7 +58,7 @@ global.CompilerContext = { handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler; return handlebarsEnv.compile(template, options); - } + }, }; function safeEval(templateSpec) { diff --git a/spec/expected/help.menu.txt b/spec/expected/help.menu.txt index 834f4afe2..750aa30cd 100644 --- a/spec/expected/help.menu.txt +++ b/spec/expected/help.menu.txt @@ -1,7 +1,8 @@ Precompile handlebar templates. -Usage: handlebars [template|directory]... +Usage: handlebars.js [template|directory]... Options: + --help Outputs this message [boolean] -f, --output Output File [string] --map Source Map File [string] -a, --amd Exports amd style (require.js) [boolean] @@ -21,5 +22,4 @@ Options: -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] \ No newline at end of file + -v, --version Show version number [boolean] \ No newline at end of file diff --git a/spec/helpers.js b/spec/helpers.js index 5166d58d6..bbea2bf60 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -1,11 +1,11 @@ -describe('helpers', function() { - it('helper with complex lookup$', function() { +describe('helpers', function () { + it('helper with complex lookup$', function () { expectTemplate('{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}') .withInput({ prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], }) - .withHelper('link', function(prefix) { + .withHelper('link', function (prefix) { return ( '' + this.text + '' ); @@ -13,47 +13,47 @@ describe('helpers', function() { .toCompileTo('Goodbye'); }); - it('helper for raw block gets raw content', function() { + it('helper for raw block gets raw content', function () { expectTemplate('{{{{raw}}}} {{test}} {{{{/raw}}}}') .withInput({ test: 'hello' }) - .withHelper('raw', function(options) { + .withHelper('raw', function (options) { return options.fn(); }) .withMessage('raw block helper gets raw content') .toCompileTo(' {{test}} '); }); - it('helper for raw block gets parameters', function() { + it('helper for raw block gets parameters', function () { expectTemplate('{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}') .withInput({ test: 'hello' }) - .withHelper('raw', function(a, b, c, options) { + .withHelper('raw', function (a, b, c, options) { return options.fn() + a + b + c; }) .withMessage('raw block helper gets raw content') .toCompileTo(' {{test}} 123'); }); - describe('raw block parsing (with identity helper-function)', function() { + describe('raw block parsing (with identity helper-function)', function () { function runWithIdentityHelper(template, expected) { expectTemplate(template) - .withHelper('identity', function(options) { + .withHelper('identity', function (options) { return options.fn(); }) .toCompileTo(expected); } - it('helper for nested raw block gets raw content', function() { + it('helper for nested raw block gets raw content', function () { runWithIdentityHelper( '{{{{identity}}}} {{{{b}}}} {{{{/b}}}} {{{{/identity}}}}', ' {{{{b}}}} {{{{/b}}}} ' ); }); - it('helper for nested raw block works with empty content', function() { + it('helper for nested raw block works with empty content', function () { runWithIdentityHelper('{{{{identity}}}}{{{{/identity}}}}', ''); }); - xit('helper for nested raw block works if nested raw blocks are broken', function() { + xit('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, @@ -64,23 +64,23 @@ describe('helpers', function() { ); }); - it('helper for nested raw block closes after first matching close', function() { + it('helper for nested raw block closes after first matching close', function () { runWithIdentityHelper( '{{{{identity}}}}abc{{{{/identity}}}} {{{{identity}}}}abc{{{{/identity}}}}', 'abc abc' ); }); - it('helper for nested raw block throw exception when with missing closing braces', function() { + it('helper for nested raw block throw exception when with missing closing braces', function () { var string = '{{{{a}}}} {{{{/a'; expectTemplate(string).toThrow(); }); }); - it('helper block with identical context', function() { + it('helper block with identical context', function () { expectTemplate('{{#goodbyes}}{{name}}{{/goodbyes}}') .withInput({ name: 'Alan' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { var out = ''; var byes = ['Goodbye', 'goodbye', 'GOODBYE']; for (var i = 0, j = byes.length; i < j; i++) { @@ -91,10 +91,10 @@ describe('helpers', function() { .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); }); - it('helper block with complex lookup expression', function() { + it('helper block with complex lookup expression', function () { expectTemplate('{{#goodbyes}}{{../name}}{{/goodbyes}}') .withInput({ name: 'Alan' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { var out = ''; var byes = ['Goodbye', 'goodbye', 'GOODBYE']; for (var i = 0, j = byes.length; i < j; i++) { @@ -105,15 +105,15 @@ describe('helpers', function() { .toCompileTo('Goodbye Alan! goodbye Alan! GOODBYE Alan! '); }); - it('helper with complex lookup and nested template', function() { + it('helper with complex lookup and nested template', function () { expectTemplate( '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' ) .withInput({ prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], }) - .withHelper('link', function(prefix, options) { + .withHelper('link', function (prefix, options) { return ( 'Goodbye'); }); - it('helper with complex lookup and nested template in VM+Compiler', function() { + it('helper with complex lookup and nested template in VM+Compiler', function () { expectTemplate( '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' ) .withInput({ prefix: '/root', - goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] + goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], }) - .withHelper('link', function(prefix, options) { + .withHelper('link', function (prefix, options) { return ( 'Goodbye'); }); - it('helper returning undefined value', function() { + it('helper returning undefined value', function () { expectTemplate(' {{nothere}}') .withHelpers({ - nothere: function() {} + nothere: function () {}, }) .toCompileTo(' '); expectTemplate(' {{#nothere}}{{/nothere}}') .withHelpers({ - nothere: function() {} + nothere: function () {}, }) .toCompileTo(' '); }); - it('block helper', function() { + it('block helper', function () { expectTemplate('{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!') .withInput({ world: 'world' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { return options.fn({ text: 'GOODBYE' }); }) .withMessage('Block helper executed') .toCompileTo('GOODBYE! cruel world!'); }); - it('block helper staying in the same context', function() { + it('block helper staying in the same context', function () { expectTemplate('{{#form}}

{{name}}

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

Yehuda

'); }); - it('block helper should have context in this', function() { + it('block helper should have context in this', function () { function link(options) { return '' + options.fn(this) + ''; } @@ -194,8 +194,8 @@ describe('helpers', function() { .withInput({ people: [ { name: 'Alan', id: 1 }, - { name: 'Yehuda', id: 2 } - ] + { name: 'Yehuda', id: 2 }, + ], }) .withHelper('link', link) .toCompileTo( @@ -203,48 +203,48 @@ describe('helpers', function() { ); }); - it('block helper for undefined value', function() { + it('block helper for undefined value', function () { expectTemplate("{{#empty}}shouldn't render{{/empty}}").toCompileTo(''); }); - it('block helper passing a new context', function() { + it('block helper passing a new context', function () { expectTemplate('{{#form yehuda}}

{{name}}

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

Yehuda

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

{{name}}

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

Harold

'); }); - it('nested block helpers', function() { + it('nested block helpers', function () { expectTemplate( '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}' ) .withInput({ - yehuda: { name: 'Yehuda' } + yehuda: { name: 'Yehuda' }, }) - .withHelper('link', function(options) { + .withHelper('link', function (options) { return '' + options.fn(this) + ''; }) - .withHelper('form', function(context, options) { + .withHelper('form', function (context, options) { return '
' + options.fn(context) + '
'; }) .withMessage('Both blocks executed') .toCompileTo('

Yehuda

Hello
'); }); - it('block helper inverted sections', function() { + it('block helper inverted sections', function () { var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; function list(context, options) { if (context.length > 0) { @@ -278,24 +278,24 @@ describe('helpers', function() { expectTemplate('{{#list people}}Hello{{^}}{{message}}{{/list}}') .withInput({ people: [], - message: "Nobody's here" + message: "Nobody's here", }) .withHelpers({ list: list }) .withMessage('the context of an inverse is the parent of the block') .toCompileTo('

Nobody's here

'); }); - it('pathed lambas with parameters', function() { + it('pathed lambas with parameters', function () { var hash = { - helper: function() { + helper: function () { return 'winning'; - } + }, }; hash.hash = hash; var helpers = { - './helper': function() { + './helper': function () { return 'fail'; - } + }, }; expectTemplate('{{./helper 1}}') @@ -309,14 +309,14 @@ describe('helpers', function() { .toCompileTo('winning'); }); - describe('helpers hash', function() { - it('providing a helpers hash', function() { + describe('helpers hash', function () { + it('providing a helpers hash', function () { expectTemplate('Goodbye {{cruel}} {{world}}!') .withInput({ cruel: 'cruel' }) .withHelpers({ - world: function() { + world: function () { return 'world'; - } + }, }) .withMessage('helpers hash is available') .toCompileTo('Goodbye cruel world!'); @@ -324,21 +324,21 @@ describe('helpers', function() { expectTemplate('Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!') .withInput({ iter: [{ cruel: 'cruel' }] }) .withHelpers({ - world: function() { + world: function () { return 'world'; - } + }, }) .withMessage('helpers hash is available inside other blocks') .toCompileTo('Goodbye cruel world!'); }); - it('in cases of conflict, helpers win', function() { + it('in cases of conflict, helpers win', function () { expectTemplate('{{{lookup}}}') .withInput({ lookup: 'Explicit' }) .withHelpers({ - lookup: function() { + lookup: function () { return 'helpers'; - } + }, }) .withMessage('helpers hash has precedence escaped expansion') .toCompileTo('helpers'); @@ -346,28 +346,28 @@ describe('helpers', function() { expectTemplate('{{lookup}}') .withInput({ lookup: 'Explicit' }) .withHelpers({ - lookup: function() { + lookup: function () { return 'helpers'; - } + }, }) .withMessage('helpers hash has precedence simple expansion') .toCompileTo('helpers'); }); - it('the helpers hash is available is nested contexts', function() { + it('the helpers hash is available is nested contexts', function () { expectTemplate('{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}') .withInput({ outer: { inner: { unused: [] } } }) .withHelpers({ - helper: function() { + helper: function () { return 'helper'; - } + }, }) .withMessage('helpers hash is available in nested contexts.') .toCompileTo('helper'); }); - it('the helper hash should augment the global hash', function() { - handlebarsEnv.registerHelper('test_helper', function() { + it('the helper hash should augment the global hash', function () { + handlebarsEnv.registerHelper('test_helper', function () { return 'found it!'; }); @@ -376,37 +376,37 @@ describe('helpers', function() { ) .withInput({ cruel: 'cruel' }) .withHelpers({ - world: function() { + world: function () { return 'world!'; - } + }, }) .toCompileTo('found it! Goodbye cruel world!!'); }); }); - describe('registration', function() { - it('unregisters', function() { + describe('registration', function () { + it('unregisters', function () { handlebarsEnv.helpers = {}; - handlebarsEnv.registerHelper('foo', function() { + handlebarsEnv.registerHelper('foo', function () { return 'fail'; }); handlebarsEnv.unregisterHelper('foo'); equals(handlebarsEnv.helpers.foo, undefined); }); - it('allows multiple globals', function() { + it('allows multiple globals', function () { var helpers = handlebarsEnv.helpers; handlebarsEnv.helpers = {}; handlebarsEnv.registerHelper({ if: helpers['if'], - world: function() { + world: function () { return 'world!'; }, - testHelper: function() { + testHelper: function () { return 'found it!'; - } + }, }); expectTemplate( @@ -416,17 +416,17 @@ describe('helpers', function() { .toCompileTo('found it! Goodbye cruel world!!'); }); - it('fails with multiple and args', function() { + it('fails with multiple and args', function () { shouldThrow( - function() { + function () { handlebarsEnv.registerHelper( { - world: function() { + world: function () { return 'world!'; }, - testHelper: function() { + testHelper: function () { return 'found it!'; - } + }, }, {} ); @@ -437,9 +437,9 @@ describe('helpers', function() { }); }); - it('decimal number literals work', function() { + it('decimal number literals work', function () { expectTemplate('Message: {{hello -1.2 1.2}}') - .withHelper('hello', function(times, times2) { + .withHelper('hello', function (times, times2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -452,9 +452,9 @@ describe('helpers', function() { .toCompileTo('Message: Hello -1.2 1.2 times'); }); - it('negative number literals work', function() { + it('negative number literals work', function () { expectTemplate('Message: {{hello -12}}') - .withHelper('hello', function(times) { + .withHelper('hello', function (times) { if (typeof times !== 'number') { times = 'NaN'; } @@ -464,10 +464,10 @@ describe('helpers', function() { .toCompileTo('Message: Hello -12 times'); }); - describe('String literal parameters', function() { - it('simple literals work', function() { + describe('String literal parameters', function () { + it('simple literals work', function () { expectTemplate('Message: {{hello "world" 12 true false}}') - .withHelper('hello', function(param, times, bool1, bool2) { + .withHelper('hello', function (param, times, bool1, bool2) { if (typeof times !== 'number') { times = 'NaN'; } @@ -485,22 +485,22 @@ describe('helpers', function() { .toCompileTo('Message: Hello world 12 times: true false'); }); - it('using a quote in the middle of a parameter raises an error', function() { + it('using a quote in the middle of a parameter raises an error', function () { expectTemplate('Message: {{hello wo"rld"}}').toThrow(Error); }); - it('escaping a String is possible', function() { + it('escaping a String is possible', function () { expectTemplate('Message: {{{hello "\\"world\\""}}}') - .withHelper('hello', function(param) { + .withHelper('hello', function (param) { return 'Hello ' + param; }) .withMessage('template with an escaped String literal') .toCompileTo('Message: Hello "world"'); }); - it("it works with ' marks", function() { + it("it works with ' marks", function () { expectTemplate('Message: {{{hello "Alan\'s world"}}}') - .withHelper('hello', function(param) { + .withHelper('hello', function (param) { return 'Hello ' + param; }) .withMessage("template with a ' mark") @@ -508,9 +508,9 @@ describe('helpers', function() { }); }); - it('negative number literals work', function() { + it('negative number literals work', function () { expectTemplate('Message: {{hello -12}}') - .withHelper('hello', function(times) { + .withHelper('hello', function (times) { if (typeof times !== 'number') { times = 'NaN'; } @@ -520,23 +520,23 @@ describe('helpers', function() { .toCompileTo('Message: Hello -12 times'); }); - describe('multiple parameters', function() { - it('simple multi-params work', function() { + describe('multiple parameters', function () { + it('simple multi-params work', function () { expectTemplate('Message: {{goodbye cruel world}}') .withInput({ cruel: 'cruel', world: 'world' }) - .withHelper('goodbye', function(cruel, world) { + .withHelper('goodbye', function (cruel, world) { return 'Goodbye ' + cruel + ' ' + world; }) .withMessage('regular helpers with multiple params') .toCompileTo('Message: Goodbye cruel world'); }); - it('block multi-params work', function() { + it('block multi-params work', function () { expectTemplate( 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}' ) .withInput({ cruel: 'cruel', world: 'world' }) - .withHelper('goodbye', function(cruel, world, options) { + .withHelper('goodbye', function (cruel, world, options) { return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world }); }) .withMessage('block helpers with multiple params') @@ -544,10 +544,10 @@ describe('helpers', function() { }); }); - describe('hash', function() { - it('helpers can take an optional hash', function() { + describe('hash', function () { + it('helpers can take an optional hash', function () { expectTemplate('{{goodbye cruel="CRUEL" world="WORLD" times=12}}') - .withHelper('goodbye', function(options) { + .withHelper('goodbye', function (options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -562,7 +562,7 @@ describe('helpers', function() { .toCompileTo('GOODBYE CRUEL WORLD 12 TIMES'); }); - it('helpers can take an optional hash with booleans', function() { + it('helpers can take an optional hash with booleans', function () { function goodbye(options) { if (options.hash.print === true) { return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world; @@ -584,9 +584,9 @@ describe('helpers', function() { .toCompileTo('NOT PRINTING'); }); - it('block helpers can take an optional hash', function() { + it('block helpers can take an optional hash', function () { expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') - .withHelper('goodbye', function(options) { + .withHelper('goodbye', function (options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -601,9 +601,9 @@ describe('helpers', function() { .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); - it('block helpers can take an optional hash with single quoted stings', function() { + it('block helpers can take an optional hash with single quoted stings', function () { expectTemplate('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}') - .withHelper('goodbye', function(options) { + .withHelper('goodbye', function (options) { return ( 'GOODBYE ' + options.hash.cruel + @@ -618,7 +618,7 @@ describe('helpers', function() { .toCompileTo('GOODBYE CRUEL world 12 TIMES'); }); - it('block helpers can take an optional hash with booleans', function() { + it('block helpers can take an optional hash with booleans', function () { function goodbye(options) { if (options.hash.print === true) { return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this); @@ -641,17 +641,17 @@ describe('helpers', function() { }); }); - describe('helperMissing', function() { - it('if a context is not found, helperMissing is used', function() { + describe('helperMissing', function () { + it('if a context is not found, helperMissing is used', function () { expectTemplate('{{hello}} {{link_to world}}').toThrow( /Missing helper: "link_to"/ ); }); - it('if a context is not found, custom helperMissing is used', function() { + it('if a context is not found, custom helperMissing is used', function () { expectTemplate('{{hello}} {{link_to world}}') .withInput({ hello: 'Hello', world: 'world' }) - .withHelper('helperMissing', function(mesg, options) { + .withHelper('helperMissing', function (mesg, options) { if (options.name === 'link_to') { return new Handlebars.SafeString('' + mesg + ''); } @@ -659,10 +659,10 @@ describe('helpers', function() { .toCompileTo('Hello world'); }); - it('if a value is not found, custom helperMissing is used', function() { + it('if a value is not found, custom helperMissing is used', function () { expectTemplate('{{hello}} {{link_to}}') .withInput({ hello: 'Hello', world: 'world' }) - .withHelper('helperMissing', function(options) { + .withHelper('helperMissing', function (options) { if (options.name === 'link_to') { return new Handlebars.SafeString('winning'); } @@ -671,175 +671,175 @@ describe('helpers', function() { }); }); - describe('knownHelpers', function() { - it('Known helper should render helper', function() { + describe('knownHelpers', function () { + it('Known helper should render helper', function () { expectTemplate('{{hello}}') .withCompileOptions({ - knownHelpers: { hello: true } + knownHelpers: { hello: true }, }) - .withHelper('hello', function() { + .withHelper('hello', function () { return 'foo'; }) .toCompileTo('foo'); }); - it('Unknown helper in knownHelpers only mode should be passed as undefined', function() { + it('Unknown helper in knownHelpers only mode should be passed as undefined', function () { expectTemplate('{{typeof hello}}') .withCompileOptions({ knownHelpers: { typeof: true }, - knownHelpersOnly: true + knownHelpersOnly: true, }) - .withHelper('typeof', function(arg) { + .withHelper('typeof', function (arg) { return typeof arg; }) - .withHelper('hello', function() { + .withHelper('hello', function () { return 'foo'; }) .toCompileTo('undefined'); }); - it('Builtin helpers available in knownHelpers only mode', function() { + it('Builtin helpers available in knownHelpers only mode', function () { expectTemplate('{{#unless foo}}bar{{/unless}}') .withCompileOptions({ - knownHelpersOnly: true + knownHelpersOnly: true, }) .toCompileTo('bar'); }); - it('Field lookup works in knownHelpers only mode', function() { + it('Field lookup works in knownHelpers only mode', function () { expectTemplate('{{foo}}') .withCompileOptions({ - knownHelpersOnly: true + knownHelpersOnly: true, }) .withInput({ foo: 'bar' }) .toCompileTo('bar'); }); - it('Conditional blocks work in knownHelpers only mode', function() { + it('Conditional blocks work in knownHelpers only mode', function () { expectTemplate('{{#foo}}bar{{/foo}}') .withCompileOptions({ - knownHelpersOnly: true + knownHelpersOnly: true, }) .withInput({ foo: 'baz' }) .toCompileTo('bar'); }); - it('Invert blocks work in knownHelpers only mode', function() { + it('Invert blocks work in knownHelpers only mode', function () { expectTemplate('{{^foo}}bar{{/foo}}') .withCompileOptions({ - knownHelpersOnly: true + knownHelpersOnly: true, }) .withInput({ foo: false }) .toCompileTo('bar'); }); - it('Functions are bound to the context in knownHelpers only mode', function() { + it('Functions are bound to the context in knownHelpers only mode', function () { expectTemplate('{{foo}}') .withCompileOptions({ - knownHelpersOnly: true + knownHelpersOnly: true, }) .withInput({ - foo: function() { + foo: function () { return this.bar; }, - bar: 'bar' + bar: 'bar', }) .toCompileTo('bar'); }); - it('Unknown helper call in knownHelpers only mode should throw', function() { + it('Unknown helper call in knownHelpers only mode should throw', function () { expectTemplate('{{typeof hello}}') .withCompileOptions({ knownHelpersOnly: true }) .toThrow(Error); }); }); - describe('blockHelperMissing', function() { - it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() { + describe('blockHelperMissing', function () { + it('lambdas are resolved by blockHelperMissing, not handlebars proper', function () { expectTemplate('{{#truthy}}yep{{/truthy}}') .withInput({ - truthy: function() { + truthy: function () { return true; - } + }, }) .toCompileTo('yep'); }); - it('lambdas resolved by blockHelperMissing are bound to the context', function() { + it('lambdas resolved by blockHelperMissing are bound to the context', function () { expectTemplate('{{#truthy}}yep{{/truthy}}') .withInput({ - truthy: function() { + truthy: function () { return this.truthiness(); }, - truthiness: function() { + truthiness: function () { return false; - } + }, }) .toCompileTo(''); }); }); - describe('name field', function() { + describe('name field', function () { var helpers = { - blockHelperMissing: function() { + blockHelperMissing: function () { return 'missing: ' + arguments[arguments.length - 1].name; }, - helperMissing: function() { + helperMissing: function () { return 'helper missing: ' + arguments[arguments.length - 1].name; }, - helper: function() { + helper: function () { return 'ran: ' + arguments[arguments.length - 1].name; - } + }, }; - it('should include in ambiguous mustache calls', function() { + it('should include in ambiguous mustache calls', function () { expectTemplate('{{helper}}') .withHelpers(helpers) .toCompileTo('ran: helper'); }); - it('should include in helper mustache calls', function() { + it('should include in helper mustache calls', function () { expectTemplate('{{helper 1}}') .withHelpers(helpers) .toCompileTo('ran: helper'); }); - it('should include in ambiguous block calls', function() { + it('should include in ambiguous block calls', function () { expectTemplate('{{#helper}}{{/helper}}') .withHelpers(helpers) .toCompileTo('ran: helper'); }); - it('should include in simple block calls', function() { + it('should include in simple block calls', function () { expectTemplate('{{#./helper}}{{/./helper}}') .withHelpers(helpers) .toCompileTo('missing: ./helper'); }); - it('should include in helper block calls', function() { + it('should include in helper block calls', function () { expectTemplate('{{#helper 1}}{{/helper}}') .withHelpers(helpers) .toCompileTo('ran: helper'); }); - it('should include in known helper calls', function() { + it('should include in known helper calls', function () { expectTemplate('{{helper}}') .withCompileOptions({ knownHelpers: { helper: true }, - knownHelpersOnly: true + knownHelpersOnly: true, }) .withHelpers(helpers) .toCompileTo('ran: helper'); }); - it('should include full id', function() { + it('should include full id', function () { expectTemplate('{{#foo.helper}}{{/foo.helper}}') .withInput({ foo: {} }) .withHelpers(helpers) .toCompileTo('missing: foo.helper'); }); - it('should include full id if a hash is passed', function() { + it('should include full id if a hash is passed', function () { expectTemplate('{{#foo.helper bar=baz}}{{/foo.helper}}') .withInput({ foo: {} }) .withHelpers(helpers) @@ -847,136 +847,136 @@ describe('helpers', function() { }); }); - describe('name conflicts', function() { - it('helpers take precedence over same-named context properties', function() { + describe('name conflicts', function () { + it('helpers take precedence over same-named context properties', function () { expectTemplate('{{goodbye}} {{cruel world}}') - .withHelper('goodbye', function() { + .withHelper('goodbye', function () { return this.goodbye.toUpperCase(); }) - .withHelper('cruel', function(world) { + .withHelper('cruel', function (world) { return 'cruel ' + world.toUpperCase(); }) .withInput({ goodbye: 'goodbye', - world: 'world' + world: 'world', }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD'); }); - it('helpers take precedence over same-named context properties$', function() { + it('helpers take precedence over same-named context properties$', function () { expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}}') - .withHelper('goodbye', function(options) { + .withHelper('goodbye', function (options) { return this.goodbye.toUpperCase() + options.fn(this); }) - .withHelper('cruel', function(world) { + .withHelper('cruel', function (world) { return 'cruel ' + world.toUpperCase(); }) .withInput({ goodbye: 'goodbye', - world: 'world' + world: 'world', }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD'); }); - it('Scoped names take precedence over helpers', function() { + it('Scoped names take precedence over helpers', function () { expectTemplate('{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}') - .withHelper('goodbye', function() { + .withHelper('goodbye', function () { return this.goodbye.toUpperCase(); }) - .withHelper('cruel', function(world) { + .withHelper('cruel', function (world) { return 'cruel ' + world.toUpperCase(); }) .withInput({ goodbye: 'goodbye', - world: 'world' + world: 'world', }) .withMessage('Helper not executed') .toCompileTo('goodbye cruel WORLD cruel GOODBYE'); }); - it('Scoped names take precedence over block helpers', function() { + it('Scoped names take precedence over block helpers', function () { expectTemplate( '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' ) - .withHelper('goodbye', function(options) { + .withHelper('goodbye', function (options) { return this.goodbye.toUpperCase() + options.fn(this); }) - .withHelper('cruel', function(world) { + .withHelper('cruel', function (world) { return 'cruel ' + world.toUpperCase(); }) .withInput({ goodbye: 'goodbye', - world: 'world' + world: 'world', }) .withMessage('Helper executed') .toCompileTo('GOODBYE cruel WORLD goodbye'); }); }); - describe('block params', function() { - it('should take presedence over context values', function() { + describe('block params', function () { + it('should take presedence over context values', function () { expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') .withInput({ value: 'foo' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { equals(options.fn.blockParams, 1); return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); }) .toCompileTo('1foo'); }); - it('should take presedence over helper values', function() { + it('should take presedence over helper values', function () { expectTemplate('{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}') - .withHelper('value', function() { + .withHelper('value', function () { return 'foo'; }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { equals(options.fn.blockParams, 1); return options.fn({}, { blockParams: [1, 2] }); }) .toCompileTo('1foo'); }); - it('should not take presedence over pathed values', function() { + it('should not take presedence over pathed values', function () { expectTemplate( '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' ) .withInput({ value: 'bar' }) - .withHelper('value', function() { + .withHelper('value', function () { return 'foo'; }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { equals(options.fn.blockParams, 1); return options.fn(this, { blockParams: [1, 2] }); }) .toCompileTo('barfoo'); }); - it('should take presednece over parent block params', function() { + it('should take presednece over parent block params', function () { var value = 1; expectTemplate( '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' ) .withInput({ value: 'foo' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { return options.fn( { value: 'bar' }, { blockParams: - options.fn.blockParams === 1 ? [value++, value++] : undefined + options.fn.blockParams === 1 ? [value++, value++] : undefined, } ); }) .toCompileTo('13foo'); }); - it('should allow block params on chained helpers', function() { + it('should allow block params on chained helpers', function () { expectTemplate( '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' ) .withInput({ value: 'foo' }) - .withHelper('goodbyes', function(options) { + .withHelper('goodbyes', function (options) { equals(options.fn.blockParams, 1); return options.fn({ value: 'bar' }, { blockParams: [1, 2] }); }) @@ -984,58 +984,58 @@ describe('helpers', function() { }); }); - describe('built-in helpers malformed arguments ', function() { - it('if helper - too few arguments', function() { + describe('built-in helpers malformed arguments ', function () { + it('if helper - too few arguments', function () { expectTemplate('{{#if}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); - it('if helper - too many arguments, string', function() { + it('if helper - too many arguments, string', function () { expectTemplate('{{#if test "string"}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); - it('if helper - too many arguments, undefined', function() { + it('if helper - too many arguments, undefined', function () { expectTemplate('{{#if test undefined}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); - it('if helper - too many arguments, null', function() { + it('if helper - too many arguments, null', function () { expectTemplate('{{#if test null}}{{/if}}').toThrow( /#if requires exactly one argument/ ); }); - it('unless helper - too few arguments', function() { + it('unless helper - too few arguments', function () { expectTemplate('{{#unless}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); - it('unless helper - too many arguments', function() { + it('unless helper - too many arguments', function () { expectTemplate('{{#unless test null}}{{/unless}}').toThrow( /#unless requires exactly one argument/ ); }); - it('with helper - too few arguments', function() { + it('with helper - too few arguments', function () { expectTemplate('{{#with}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); - it('with helper - too many arguments', function() { + it('with helper - too many arguments', function () { expectTemplate('{{#with test "string"}}{{/with}}').toThrow( /#with requires exactly one argument/ ); }); }); - describe('the lookupProperty-option', function() { - it('should be passed to custom helpers', function() { + describe('the lookupProperty-option', function () { + it('should be passed to custom helpers', function () { expectTemplate('{{testHelper}}') .withHelper('testHelper', function testHelper(options) { return options.lookupProperty(this, 'testProperty'); diff --git a/spec/index.html b/spec/index.html index 3f2068ed1..c4c582b2e 100644 --- a/spec/index.html +++ b/spec/index.html @@ -28,7 +28,6 @@ mocha.setup('bdd'); - - @@ -54,7 +53,7 @@ } var runner = mocha.run(); - //Reporting for saucelabs + // Reporting to test-runner var failedTests = []; runner.on('end', function(){ window.mochaResults = runner.stats; diff --git a/spec/umd.html b/spec/umd.html index ae461ed69..4137f1466 100644 --- a/spec/umd.html +++ b/spec/umd.html @@ -27,7 +27,6 @@ window.expect = chai.expect; mocha.setup('bdd'); - @@ -74,7 +73,7 @@ } var runner = mocha.run(); - //Reporting for saucelabs + // Reporting to test-runner var failedTests = []; runner.on('end', function(){ window.mochaResults = runner.stats; diff --git a/spec/utils.js b/spec/utils.js index f4a4e3e4f..0ef90e9b2 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -1,6 +1,6 @@ -describe('utils', function() { - describe('#SafeString', function() { - it('constructing a safestring from a string and checking its type', function() { +describe('utils', function () { + describe('#SafeString', function () { + it('constructing a safestring from a string and checking its type', function () { var safe = new Handlebars.SafeString('testing 1, 2, 3'); if (!(safe instanceof Handlebars.SafeString)) { throw new Error('Must be instance of SafeString'); @@ -12,7 +12,7 @@ describe('utils', function() { ); }); - it('it should not escape SafeString properties', function() { + it('it should not escape SafeString properties', function () { var name = new Handlebars.SafeString('Sean O'Malley'); expectTemplate('{{name}}') @@ -21,26 +21,26 @@ describe('utils', function() { }); }); - describe('#escapeExpression', function() { - it('shouhld escape html', function() { + describe('#escapeExpression', function () { + it('should escape html', function () { equals( Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>' ); equals(Handlebars.Utils.escapeExpression('foo='), 'foo='); }); - it('should not escape SafeString', function() { + it('should not escape SafeString', function () { var string = new Handlebars.SafeString('foo<&"\'>'); equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>'); var obj = { - toHTML: function() { + toHTML: function () { return 'foo<&"\'>'; - } + }, }; equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>'); }); - it('should handle falsy', function() { + it('should handle falsy', function () { equals(Handlebars.Utils.escapeExpression(''), ''); equals(Handlebars.Utils.escapeExpression(undefined), ''); equals(Handlebars.Utils.escapeExpression(null), ''); @@ -48,14 +48,14 @@ describe('utils', function() { equals(Handlebars.Utils.escapeExpression(false), 'false'); equals(Handlebars.Utils.escapeExpression(0), '0'); }); - it('should handle empty objects', function() { + it('should handle empty objects', function () { equals(Handlebars.Utils.escapeExpression({}), {}.toString()); equals(Handlebars.Utils.escapeExpression([]), [].toString()); }); }); - describe('#isEmpty', function() { - it('should not be empty', function() { + 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); @@ -63,7 +63,7 @@ describe('utils', function() { equals(Handlebars.Utils.isEmpty([]), true); }); - it('should be empty', function() { + it('should be empty', function () { equals(Handlebars.Utils.isEmpty(0), false); equals(Handlebars.Utils.isEmpty([1]), false); equals(Handlebars.Utils.isEmpty('foo'), false); @@ -71,8 +71,8 @@ describe('utils', function() { }); }); - describe('#extend', function() { - it('should ignore prototype values', function() { + describe('#extend', function () { + it('should ignore prototype values', function () { function A() { this.a = 1; } diff --git a/spec/vendor/json2.js b/spec/vendor/json2.js deleted file mode 100644 index deb88ec9a..000000000 --- a/spec/vendor/json2.js +++ /dev/null @@ -1,489 +0,0 @@ -/* - json2.js - 2014-02-04 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, regexp: true */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (typeof JSON !== 'object') { - JSON = {}; -} - -(function () { - 'use strict'; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function () { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function () { - return this.valueOf(); - }; - } - - var cx, - escapable, - gap, - indent, - meta, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); diff --git a/spec/vendor/require.js b/spec/vendor/require.js index 2ce09b5e3..05dc42fc9 100644 --- a/spec/vendor/require.js +++ b/spec/vendor/require.js @@ -1488,7 +1488,7 @@ var requirejs, require, define; /** * Called to enable a module if it is still in the registry * awaiting enablement. A second arg, parent, the parent module, - * is passed in for context, when this method is overriden by + * is passed in for context, when this method is overridden by * the optimizer. Not shown here to keep code compact. */ enable: function (depMap) { diff --git a/spec/visitor.js b/spec/visitor.js deleted file mode 100644 index 2bec356fe..000000000 --- a/spec/visitor.js +++ /dev/null @@ -1,164 +0,0 @@ -describe('Visitor', function() { - if (!Handlebars.Visitor || !Handlebars.print) { - return; - } - - it('should provide coverage', function() { - // Simply run the thing and make sure it does not fail and that all of the - // stub methods are executed - var visitor = new Handlebars.Visitor(); - visitor.accept( - Handlebars.parse( - '{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}' - ) - ); - visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}')); - visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}')); - visitor.accept(Handlebars.parse('{{* bar }}')); - }); - - it('should traverse to stubs', function() { - var visitor = new Handlebars.Visitor(); - - visitor.StringLiteral = function(string) { - equal(string.value, '2'); - }; - visitor.NumberLiteral = function(number) { - equal(number.value, 1); - }; - visitor.BooleanLiteral = function(bool) { - equal(bool.value, true); - - equal(this.parents.length, 3); - equal(this.parents[0].type, 'SubExpression'); - equal(this.parents[1].type, 'BlockStatement'); - equal(this.parents[2].type, 'Program'); - }; - visitor.PathExpression = function(id) { - equal(/(foo\.)?bar$/.test(id.original), true); - }; - visitor.ContentStatement = function(content) { - equal(content.value, ' '); - }; - visitor.CommentStatement = function(comment) { - equal(comment.value, 'comment'); - }; - - visitor.accept( - Handlebars.parse( - '{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}' - ) - ); - }); - - describe('mutating', function() { - describe('fields', function() { - it('should replace value', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.StringLiteral = function(string) { - return { type: 'NumberLiteral', value: 42, loc: string.loc }; - }; - - var ast = Handlebars.parse('{{foo foo="foo"}}'); - visitor.accept(ast); - equals( - Handlebars.print(ast), - '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n' - ); - }); - it('should treat undefined resonse as identity', function() { - var visitor = new Handlebars.Visitor(); - visitor.mutating = true; - - var ast = Handlebars.parse('{{foo foo=42}}'); - visitor.accept(ast); - equals( - Handlebars.print(ast), - '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n' - ); - }); - it('should remove false responses', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.Hash = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo foo=42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n'); - }); - it('should throw when removing required values', function() { - shouldThrow( - function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.PathExpression = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - }, - Handlebars.Exception, - 'MustacheStatement requires path' - ); - }); - it('should throw when returning non-node responses', function() { - shouldThrow( - function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.PathExpression = function() { - return {}; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - }, - Handlebars.Exception, - 'Unexpected node type "undefined" found when accepting path on MustacheStatement' - ); - }); - }); - describe('arrays', function() { - it('should replace value', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.StringLiteral = function(string) { - return { type: 'NumberLiteral', value: 42, loc: string.locInfo }; - }; - - var ast = Handlebars.parse('{{foo "foo"}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n'); - }); - it('should treat undefined resonse as identity', function() { - var visitor = new Handlebars.Visitor(); - visitor.mutating = true; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n'); - }); - it('should remove false responses', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.NumberLiteral = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n'); - }); - }); - }); -}); diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js index f826d95a1..cea4249b5 100644 --- a/spec/whitespace-control.js +++ b/spec/whitespace-control.js @@ -1,32 +1,22 @@ -describe('whitespace control', function() { - it('should strip whitespace around mustache calls', function() { +describe('whitespace control', function () { + it('should strip whitespace around mustache calls', function () { var hash = { foo: 'bar<' }; - expectTemplate(' {{~foo~}} ') - .withInput(hash) - .toCompileTo('bar<'); + expectTemplate(' {{~foo~}} ').withInput(hash).toCompileTo('bar<'); - expectTemplate(' {{~foo}} ') - .withInput(hash) - .toCompileTo('bar< '); + expectTemplate(' {{~foo}} ').withInput(hash).toCompileTo('bar< '); - expectTemplate(' {{foo~}} ') - .withInput(hash) - .toCompileTo(' bar<'); + expectTemplate(' {{foo~}} ').withInput(hash).toCompileTo(' bar<'); - expectTemplate(' {{~&foo~}} ') - .withInput(hash) - .toCompileTo('bar<'); + expectTemplate(' {{~&foo~}} ').withInput(hash).toCompileTo('bar<'); - expectTemplate(' {{~{foo}~}} ') - .withInput(hash) - .toCompileTo('bar<'); + expectTemplate(' {{~{foo}~}} ').withInput(hash).toCompileTo('bar<'); expectTemplate('1\n{{foo~}} \n\n 23\n{{bar}}4').toCompileTo('1\n23\n4'); }); - describe('blocks', function() { - it('should strip whitespace around simple block calls', function() { + describe('blocks', function () { + it('should strip whitespace around simple block calls', function () { var hash = { foo: 'bar<' }; expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') @@ -54,7 +44,7 @@ describe('whitespace control', function() { .toCompileTo(' abara '); }); - it('should strip whitespace around inverse block calls', function() { + it('should strip whitespace around inverse block calls', function () { expectTemplate(' {{~^if foo~}} bar {{~/if~}} ').toCompileTo('bar'); expectTemplate(' {{^if foo~}} bar {{/if~}} ').toCompileTo(' bar '); @@ -68,7 +58,7 @@ describe('whitespace control', function() { ).toCompileTo('bar'); }); - it('should strip whitespace around complex block calls', function() { + it('should strip whitespace around complex block calls', function () { var hash = { foo: 'bar<' }; expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') @@ -127,7 +117,7 @@ describe('whitespace control', function() { }); }); - it('should strip whitespace around partials', function() { + it('should strip whitespace around partials', function () { expectTemplate('foo {{~> dude~}} ') .withPartials({ dude: 'bar' }) .toCompileTo('foobar'); @@ -149,7 +139,7 @@ describe('whitespace control', function() { .toCompileTo('foo\n bar'); }); - it('should only strip whitespace once', function() { + it('should only strip whitespace once', function () { expectTemplate(' {{~foo~}} {{foo}} {{foo}} ') .withInput({ foo: 'bar' }) .toCompileTo('barbar bar '); diff --git a/src/handlebars.l b/src/handlebars.l deleted file mode 100644 index fbf208b48..000000000 --- a/src/handlebars.l +++ /dev/null @@ -1,126 +0,0 @@ - -%x mu emu com raw - -%{ - -function strip(start, end) { - return yytext = yytext.substring(start, yyleng - end + start); -} - -%} - -LEFT_STRIP "~" -RIGHT_STRIP "~" - -LOOKAHEAD [=~}\s\/.)|] -LITERAL_LOOKAHEAD [~}\s)] - -/* -ID is the inverse of control characters. -Control characters ranges: - [\s] Whitespace - [!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, - - [;->@] ;, <, =, >, @, Exceptions in range: :, ? - [\[-\^`] [, \, ], ^, `, Exceptions in range: _ - [\{-~] {, |, }, ~ -*/ -ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} - -%% - -[^\x00]*?/("{{") { - if(yytext.slice(-2) === "\\\\") { - strip(0,1); - this.begin("mu"); - } else if(yytext.slice(-1) === "\\") { - strip(0,1); - this.begin("emu"); - } else { - this.begin("mu"); - } - if(yytext) return 'CONTENT'; - } - -[^\x00]+ return 'CONTENT'; - -// marks CONTENT up to the next mustache or escaped mustache -[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<>) { - this.popState(); - return 'CONTENT'; - } - -// nested raw block will create stacked 'raw' condition -"{{{{"/[^/] this.begin('raw'); return 'CONTENT'; -"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { - this.popState(); - // Should be using `this.topState()` below, but it currently - // returns the second top instead of the first top. Opened an - // issue about it at https://github.com/zaach/jison/issues/291 - if (this.conditionStack[this.conditionStack.length-1] === 'raw') { - return 'CONTENT'; - } else { - strip(5, 9); - return 'END_RAW_BLOCK'; - } - } -[^\x00]+?/("{{{{") { return 'CONTENT'; } - -[\s\S]*?"--"{RIGHT_STRIP}?"}}" { - this.popState(); - return 'COMMENT'; -} - -"(" return 'OPEN_SEXPR'; -")" return 'CLOSE_SEXPR'; - -"{{{{" { return 'OPEN_RAW_BLOCK'; } -"}}}}" { - this.popState(); - this.begin('raw'); - return 'CLOSE_RAW_BLOCK'; - } -"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; -"{{"{LEFT_STRIP}?"#>" return 'OPEN_PARTIAL_BLOCK'; -"{{"{LEFT_STRIP}?"#""*"? return 'OPEN_BLOCK'; -"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; -"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE'; -"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE'; -"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE'; -"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN'; -"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED'; -"{{"{LEFT_STRIP}?"&" return 'OPEN'; -"{{"{LEFT_STRIP}?"!--" { - this.unput(yytext); - this.popState(); - this.begin('com'); -} -"{{"{LEFT_STRIP}?"!"[\s\S]*?"}}" { - this.popState(); - return 'COMMENT'; -} -"{{"{LEFT_STRIP}?"*"? return 'OPEN'; - -"=" return 'EQUALS'; -".." return 'ID'; -"."/{LOOKAHEAD} return 'ID'; -[\/.] return 'SEP'; -\s+ // ignore whitespace -"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED'; -{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE'; -'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING'; -"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING'; -"@" return 'DATA'; -"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -"undefined"/{LITERAL_LOOKAHEAD} return 'UNDEFINED'; -"null"/{LITERAL_LOOKAHEAD} return 'NULL'; -\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER'; -"as"\s+"|" return 'OPEN_BLOCK_PARAMS'; -"|" return 'CLOSE_BLOCK_PARAMS'; - -{ID} return 'ID'; - -'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID'; -. return 'INVALID'; - -<> return 'EOF'; diff --git a/src/handlebars.yy b/src/handlebars.yy deleted file mode 100644 index cab04c61a..000000000 --- a/src/handlebars.yy +++ /dev/null @@ -1,166 +0,0 @@ -%start root - -%ebnf - -%% - -root - : program EOF { return $1; } - ; - -program - : statement* -> yy.prepareProgram($1) - ; - -statement - : mustache -> $1 - | block -> $1 - | rawBlock -> $1 - | partial -> $1 - | partialBlock -> $1 - | content -> $1 - | COMMENT { - $$ = { - type: 'CommentStatement', - value: yy.stripComment($1), - strip: yy.stripFlags($1, $1), - loc: yy.locInfo(@$) - }; - }; - -content - : CONTENT { - $$ = { - type: 'ContentStatement', - original: $1, - value: $1, - loc: yy.locInfo(@$) - }; - }; - -rawBlock - : openRawBlock content* END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$) - ; - -openRawBlock - : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK -> { path: $2, params: $3, hash: $4 } - ; - -block - : openBlock program inverseChain? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$) - | openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$) - ; - -openBlock - : OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { open: $1, path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -openInverse - : OPEN_INVERSE helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -openInverseChain - : OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -inverseAndProgram - : INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 } - ; - -inverseChain - : openInverseChain program inverseChain? { - var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$), - program = yy.prepareProgram([inverse], $2.loc); - program.chained = true; - - $$ = { strip: $1.strip, program: program, chain: true }; - } - | inverseAndProgram -> $1 - ; - -closeBlock - : OPEN_ENDBLOCK helperName CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)} - ; - -mustache - // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. - // This also allows for handler unification as all mustache node instances can utilize the same handler - : OPEN helperName param* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) - | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) - ; - -partial - : OPEN_PARTIAL partialName param* hash? CLOSE { - $$ = { - type: 'PartialStatement', - name: $2, - params: $3, - hash: $4, - indent: '', - strip: yy.stripFlags($1, $5), - loc: yy.locInfo(@$) - }; - } - ; -partialBlock - : openPartialBlock program closeBlock -> yy.preparePartialBlock($1, $2, $3, @$) - ; -openPartialBlock - : OPEN_PARTIAL_BLOCK partialName param* hash? CLOSE -> { path: $2, params: $3, hash: $4, strip: yy.stripFlags($1, $5) } - ; - -param - : helperName -> $1 - | sexpr -> $1 - ; - -sexpr - : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR { - $$ = { - type: 'SubExpression', - path: $2, - params: $3, - hash: $4, - loc: yy.locInfo(@$) - }; - }; - -hash - : hashSegment+ -> {type: 'Hash', pairs: $1, loc: yy.locInfo(@$)} - ; - -hashSegment - : ID EQUALS param -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)} - ; - -blockParams - : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2) - ; - -helperName - : path -> $1 - | dataName -> $1 - | STRING -> {type: 'StringLiteral', value: $1, original: $1, loc: yy.locInfo(@$)} - | NUMBER -> {type: 'NumberLiteral', value: Number($1), original: Number($1), loc: yy.locInfo(@$)} - | BOOLEAN -> {type: 'BooleanLiteral', value: $1 === 'true', original: $1 === 'true', loc: yy.locInfo(@$)} - | UNDEFINED -> {type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(@$)} - | NULL -> {type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(@$)} - ; - -partialName - : helperName -> $1 - | sexpr -> $1 - ; - -dataName - : DATA pathSegments -> yy.preparePath(true, $2, @$) - ; - -path - : pathSegments -> yy.preparePath(false, $1, @$) - ; - -pathSegments - : pathSegments SEP ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; } - | ID -> [{part: yy.id($1), original: $1}] - ; diff --git a/src/parser-prefix.js b/src/parser-prefix.js deleted file mode 100644 index d9ed04116..000000000 --- a/src/parser-prefix.js +++ /dev/null @@ -1 +0,0 @@ -// File ignored in coverage tests via setting in .istanbul.yml diff --git a/src/parser-suffix.js b/src/parser-suffix.js deleted file mode 100644 index 6e4aa20d6..000000000 --- a/src/parser-suffix.js +++ /dev/null @@ -1 +0,0 @@ -export default handlebars; diff --git a/tasks/.eslintrc.js b/tasks/.eslintrc.js index 642a9ab06..aa9378534 100644 --- a/tasks/.eslintrc.js +++ b/tasks/.eslintrc.js @@ -1,14 +1,8 @@ module.exports = { - extends: ['../.eslintrc.js'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2017, - ecmaFeatures: {} - }, rules: { 'no-process-env': 'off', 'prefer-const': 'warn', 'compat/compat': 'off', - 'dot-notation': ['error', { allowKeywords: true }] - } + 'dot-notation': ['error', { allowKeywords: true }], + }, }; diff --git a/tasks/metrics.js b/tasks/metrics.js index acc3cece4..15946c5cd 100644 --- a/tasks/metrics.js +++ b/tasks/metrics.js @@ -1,14 +1,14 @@ -const metrics = require('../bench'); +const metrics = require('../tests/bench'); const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); -module.exports = function(grunt) { +module.exports = function (grunt) { const registerAsyncTask = createRegisterAsyncTaskFn(grunt); - registerAsyncTask('metrics', function() { + registerAsyncTask('metrics', function () { const onlyExecuteName = grunt.option('name'); const events = {}; - const promises = Object.keys(metrics).map(async name => { + const promises = Object.keys(metrics).map(async (name) => { if (/^_/.test(name)) { return; } @@ -16,8 +16,8 @@ module.exports = function(grunt) { return; } - return new Promise(resolve => { - metrics[name](grunt, function(data) { + return new Promise((resolve) => { + metrics[name](grunt, function (data) { events[name] = data; resolve(); }); diff --git a/tasks/parser.js b/tasks/parser.js deleted file mode 100644 index 252b8c267..000000000 --- a/tasks/parser.js +++ /dev/null @@ -1,33 +0,0 @@ -const { execFileWithInheritedOutput } = require('./util/exec-file'); -const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); - -const OUTPUT_FILE = 'lib/handlebars/compiler/parser.js'; - -module.exports = function(grunt) { - const registerAsyncTask = createRegisterAsyncTaskFn(grunt); - - registerAsyncTask('parser', async () => { - await runJison(); - combineWithPrefixAndSuffix(); - grunt.log.writeln(`Parser "${OUTPUT_FILE}" created.`); - }); - - async function runJison() { - await execFileWithInheritedOutput('jison', [ - '-m', - 'js', - 'src/handlebars.yy', - 'src/handlebars.l' - ]); - } - - function combineWithPrefixAndSuffix() { - const combinedParserSourceCode = - grunt.file.read('src/parser-prefix.js') + - grunt.file.read('handlebars.js') + - grunt.file.read('src/parser-suffix.js'); - - grunt.file.write(OUTPUT_FILE, combinedParserSourceCode); - grunt.file.delete('handlebars.js'); - } -}; diff --git a/tasks/publish-to-aws.js b/tasks/publish-to-aws.js index 49655fa17..a41cb02a4 100644 --- a/tasks/publish-to-aws.js +++ b/tasks/publish-to-aws.js @@ -3,7 +3,7 @@ const git = require('./util/git'); const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); const semver = require('semver'); -module.exports = function(grunt) { +module.exports = function (grunt) { const registerAsyncTask = createRegisterAsyncTaskFn(grunt); registerAsyncTask('publish-to-aws', async () => { @@ -48,7 +48,7 @@ module.exports = function(grunt) { } async function publish(suffixes) { - const publishPromises = suffixes.map(suffix => publishSuffix(suffix)); + const publishPromises = suffixes.map((suffix) => publishSuffix(suffix)); return Promise.all(publishPromises); } @@ -57,9 +57,9 @@ module.exports = function(grunt) { 'handlebars.js', 'handlebars.min.js', 'handlebars.runtime.js', - 'handlebars.runtime.min.js' + 'handlebars.runtime.min.js', ]; - const publishPromises = filenames.map(async filename => { + const publishPromises = filenames.map(async (filename) => { const nameInBucket = getNameInBucket(filename, suffix); const localFile = getLocalFile(filename); await uploadToBucket(localFile, nameInBucket); @@ -75,7 +75,7 @@ module.exports = function(grunt) { const uploadParams = { Bucket: bucket, Key: nameInBucket, - Body: grunt.file.read(localFile) + Body: grunt.file.read(localFile), }; return s3PutObject(uploadParams); } @@ -84,7 +84,7 @@ module.exports = function(grunt) { function s3PutObject(uploadParams) { const s3 = new AWS.S3(); return new Promise((resolve, reject) => { - s3.putObject(uploadParams, err => { + s3.putObject(uploadParams, (err) => { if (err != null) { return reject(err); } diff --git a/tasks/task-tests/.eslintrc.js b/tasks/task-tests/.eslintrc.js deleted file mode 100644 index 604aeb2c0..000000000 --- a/tasks/task-tests/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - extends: '../../.eslintrc.js', - env: { - mocha: true - }, - parserOptions: { - ecmaVersion: 2018 - } -}; diff --git a/tasks/task-tests/README.md b/tasks/task-tests/README.md deleted file mode 100644 index f4a88806c..000000000 --- a/tasks/task-tests/README.md +++ /dev/null @@ -1 +0,0 @@ -Use `mocha tasks/task-tests` to run these tests diff --git a/tasks/test-bin.js b/tasks/test-bin.js index 58dfab042..e351948e6 100644 --- a/tasks/test-bin.js +++ b/tasks/test-bin.js @@ -11,47 +11,47 @@ const testCases = [ { binInputParameters: ['-a', 'spec/artifacts/empty.handlebars'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.amd.js' + expectedOutputSpec: './spec/expected/empty.amd.js', }, { binInputParameters: [ '-a', '-f', 'TEST_OUTPUT', - 'spec/artifacts/empty.handlebars' + 'spec/artifacts/empty.handlebars', ], outputLocation: 'TEST_OUTPUT', - expectedOutputSpec: './spec/expected/empty.amd.js' + expectedOutputSpec: './spec/expected/empty.amd.js', }, { binInputParameters: [ '-a', '-n', 'CustomNamespace.templates', - 'spec/artifacts/empty.handlebars' + 'spec/artifacts/empty.handlebars', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.amd.namespace.js' + expectedOutputSpec: './spec/expected/empty.amd.namespace.js', }, { binInputParameters: [ '-a', '--namespace', 'CustomNamespace.templates', - 'spec/artifacts/empty.handlebars' + 'spec/artifacts/empty.handlebars', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.amd.namespace.js' + expectedOutputSpec: './spec/expected/empty.amd.namespace.js', }, { binInputParameters: ['-a', '-s', 'spec/artifacts/empty.handlebars'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.amd.simple.js' + expectedOutputSpec: './spec/expected/empty.amd.simple.js', }, { binInputParameters: ['-a', '-m', 'spec/artifacts/empty.handlebars'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.amd.min.js' + expectedOutputSpec: './spec/expected/empty.amd.min.js', }, { binInputParameters: [ @@ -61,44 +61,44 @@ const testCases = [ 'someHelper', '-k', 'anotherHelper', - '-o' + '-o', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/non.empty.amd.known.helper.js' + expectedOutputSpec: './spec/expected/non.empty.amd.known.helper.js', }, { binInputParameters: ['--help'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/help.menu.txt' + expectedOutputSpec: './spec/expected/help.menu.txt', }, { binInputParameters: ['-v'], outputLocation: 'stdout', - expectedOutput: require('../package.json').version + expectedOutput: require('../package.json').version, }, { binInputParameters: [ '-a', '-e', 'hbs', - './spec/artifacts/non.default.extension.hbs' + './spec/artifacts/non.default.extension.hbs', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/non.default.extension.amd.js' + expectedOutputSpec: './spec/expected/non.default.extension.amd.js', }, { binInputParameters: [ '-a', '-p', - './spec/artifacts/partial.template.handlebars' + './spec/artifacts/partial.template.handlebars', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/partial.template.js' + expectedOutputSpec: './spec/expected/partial.template.js', }, { binInputParameters: ['spec/artifacts/empty.handlebars', '-c'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.common.js' + expectedOutputSpec: './spec/expected/empty.common.js', }, { binInputParameters: [ @@ -106,30 +106,30 @@ const testCases = [ 'spec/artifacts/empty.handlebars', '-a', '-n', - 'someNameSpace' + 'someNameSpace', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/namespace.amd.js' + expectedOutputSpec: './spec/expected/namespace.amd.js', }, { binInputParameters: [ 'spec/artifacts/empty.handlebars', '-h', 'some-path/', - '-a' + '-a', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/handlebar.path.amd.js' + expectedOutputSpec: './spec/expected/handlebar.path.amd.js', }, { binInputParameters: [ 'spec/artifacts/partial.template.handlebars', '-r', 'spec', - '-a' + '-a', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.root.amd.js' + expectedOutputSpec: './spec/expected/empty.root.amd.js', }, { binInputParameters: [ @@ -141,10 +141,10 @@ const testCases = [ 'firstTemplate', '-N', 'secondTemplate', - '-a' + '-a', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/empty.name.amd.js' + expectedOutputSpec: './spec/expected/empty.name.amd.js', }, { binInputParameters: [ @@ -155,26 +155,36 @@ const testCases = [ '-N', 'test', '--map', - './spec/tmp/source.map.amd.txt' + './spec/tmp/source.map.amd.txt', ], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/source.map.amd.js' + expectedOutputSpec: './spec/expected/source.map.amd.js', }, { binInputParameters: ['./spec/artifacts/bom.handlebars', '-b', '-a'], outputLocation: 'stdout', - expectedOutputSpec: './spec/expected/bom.amd.js' - } + 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() { +module.exports = function (grunt) { + grunt.registerTask('test:bin', function () { testCases.forEach( ({ binInputParameters, outputLocation, expectedOutputSpec, - expectedOutput + expectedOutput, }) => { const stdout = executeBinHandlebars(...binInputParameters); @@ -195,7 +205,7 @@ module.exports = function(grunt) { expect(normalizedOutput).not.to.be.differentFrom( normalizedExpectedOutput, { - relaxedSpace: true + relaxedSpace: true, } ); } @@ -209,9 +219,9 @@ 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'].concat(args)); + return execFilesSyncUtf8(nodeJs, ['./bin/handlebars.js'].concat(args)); } - return execFilesSyncUtf8('./bin/handlebars', args); + return execFilesSyncUtf8('./bin/handlebars.js', args); } function execFilesSyncUtf8(command, args) { diff --git a/tasks/test-mocha.js b/tasks/test-mocha.js index b412c8c05..956300cdc 100644 --- a/tasks/test-mocha.js +++ b/tasks/test-mocha.js @@ -2,7 +2,7 @@ const { execNodeJsScriptWithInheritedOutput } = require('./util/exec-file'); const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); const nodeJs = process.argv0; -module.exports = function(grunt) { +module.exports = function (grunt) { const registerAsyncTask = createRegisterAsyncTaskFn(grunt); registerAsyncTask('test:mocha', async () => @@ -12,7 +12,7 @@ module.exports = function(grunt) { registerAsyncTask('test:cov', async () => execNodeJsScriptWithInheritedOutput('node_modules/nyc/bin/nyc', [ nodeJs, - './spec/env/runner.js' + './spec/env/runner.js', ]) ); diff --git a/tasks/tests/.eslintrc.js b/tasks/tests/.eslintrc.js new file mode 100644 index 000000000..42275b7cd --- /dev/null +++ b/tasks/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + mocha: true, + }, +}; diff --git a/tasks/tests/README.md b/tasks/tests/README.md new file mode 100644 index 000000000..3c4051cdf --- /dev/null +++ b/tasks/tests/README.md @@ -0,0 +1 @@ +Use `mocha tasks/tests` to run these tests diff --git a/tasks/task-tests/git.test.js b/tasks/tests/git.test.js similarity index 79% rename from tasks/task-tests/git.test.js rename to tasks/tests/git.test.js index 89546a8b8..2c2b30d51 100644 --- a/tasks/task-tests/git.test.js +++ b/tasks/tests/git.test.js @@ -14,8 +14,8 @@ const remoteDir = path.join(tmpDir, 'remote-repo'); const cloneDir = path.join(tmpDir, 'clone-repo'); const oldCwd = process.cwd(); -describe('utils/git', function() { - beforeEach(async function() { +describe('utils/git', function () { + beforeEach(async function () { await fs.remove(tmpDir); await createRepositoryThatActsAsRemote(); process.chdir(tmpDir); @@ -33,12 +33,12 @@ describe('utils/git', function() { await git.commit('commit message'); } - afterEach(function() { + afterEach(function () { process.chdir(oldCwd); }); - describe('the "remotes"-function', function() { - it('should list all remotes', async function() { + describe('the "remotes"-function', function () { + it('should list all remotes', async function () { await git.git('remote', 'set-url', 'origin', 'https://test.org/test'); await git.git('remote', 'add', 'second-remote', 'https://test.org/test2'); @@ -48,13 +48,13 @@ describe('utils/git', function() { 'origin\thttps://test.org/test (fetch)', 'origin\thttps://test.org/test (push)', 'second-remote\thttps://test.org/test2 (fetch)', - 'second-remote\thttps://test.org/test2 (push)' + 'second-remote\thttps://test.org/test2 (push)', ]); }); }); - describe('the "branches"-function', function() { - it('should list all branches', async function() { + describe('the "branches"-function', function () { + it('should list all branches', async function () { await git.git('branch', 'test'); await git.git('branch', 'test2'); @@ -64,32 +64,32 @@ describe('utils/git', function() { ' test', ' test2', ' remotes/origin/HEAD -> origin/master', - ' remotes/origin/master' + ' remotes/origin/master', ]); }); }); - describe('the "commitInfo"-function', function() { - it('should list head and master sha', async function() { + describe('the "commitInfo"-function', function () { + it('should list head and master sha', async function () { const result = await git.commitInfo(); expect(result.masterSha).to.equal(result.headSha); expect(result.masterSha).to.match(/^[0-9a-f]+$/); expect(result.headSha).to.match(/^[0-9a-f]+$/); }); - it('should have "isMaster=true" if the master branch is checked out', async function() { + it('should have "isMaster=true" if the master branch is checked out', async function () { const result = await git.commitInfo(); expect(result.isMaster).to.be.true(); }); - it('should have "isMaster=true" if the current commit is the last commit of the master branch', async function() { + it('should have "isMaster=true" if the current commit is the last commit of the master branch', async function () { await git.git('checkout', '-b', 'new-branch'); const result = await git.commitInfo(); expect(result.isMaster).to.be.true(); }); - it('should have "isMaster=false" if the current commit is NOT the last commit of the master branch', async function() { + it('should have "isMaster=false" if the current commit is NOT the last commit of the master branch', async function () { await git.git('checkout', '-b', 'new-branch'); fs.writeFile('new-file.txt', 'new-file'); await git.add('new-file.txt'); @@ -99,13 +99,13 @@ describe('utils/git', function() { expect(result.isMaster).to.be.false(); }); - it('should show the current tag', async function() { + it('should show the current tag', async function () { await git.git('tag', 'test-tag'); const result = await git.commitInfo(); expect(result.tagName).to.be.equal('test-tag'); }); - it('should show a version tag rather than standard tags', async function() { + it('should show a version tag rather than standard tags', async function () { await git.git('tag', 'test-tag'); await git.git('tag', 'v1.2'); await git.git('tag', 'test-tag2'); @@ -113,7 +113,7 @@ describe('utils/git', function() { expect(result.tagName).to.be.equal('v1.2'); }); - it('should show no tag if there is no tag', async function() { + it('should show no tag if there is no tag', async function () { const result = await git.commitInfo(); expect(result.tagName).to.be.null(); }); diff --git a/tasks/task-tests/mocha.opts b/tasks/tests/mocha.opts similarity index 100% rename from tasks/task-tests/mocha.opts rename to tasks/tests/mocha.opts diff --git a/tasks/util/async-grunt-task.js b/tasks/util/async-grunt-task.js index 20360570f..432d68afd 100644 --- a/tasks/util/async-grunt-task.js +++ b/tasks/util/async-grunt-task.js @@ -2,9 +2,9 @@ module.exports = { createRegisterAsyncTaskFn }; function createRegisterAsyncTaskFn(grunt) { return function registerAsyncTask(name, asyncFunction) { - grunt.registerTask(name, function() { + grunt.registerTask(name, function () { asyncFunction() - .catch(error => { + .catch((error) => { grunt.fatal(error); }) .finally(this.async()); diff --git a/tasks/util/exec-file.js b/tasks/util/exec-file.js index 66fd8e92a..1aaefbca1 100644 --- a/tasks/util/exec-file.js +++ b/tasks/util/exec-file.js @@ -1,16 +1,13 @@ const childProcess = require('child_process'); -const fs = require('fs'); -const path = require('path'); module.exports = { execNodeJsScriptWithInheritedOutput, - execFileWithInheritedOutput }; async function execNodeJsScriptWithInheritedOutput(command, args) { return new Promise((resolve, reject) => { const child = childProcess.fork(command, args, { stdio: 'inherit' }); - child.on('close', code => { + child.on('close', (code) => { if (code !== 0) { reject(new Error(`Child process failed with exit-code ${code}`)); } @@ -18,34 +15,3 @@ async function execNodeJsScriptWithInheritedOutput(command, args) { }); }); } - -async function execFileWithInheritedOutput(command, args) { - return new Promise((resolve, reject) => { - const resolvedCommand = preferLocalDependencies(command); - const child = childProcess.spawn(resolvedCommand, args, { - stdio: 'inherit' - }); - child.on('exit', code => { - if (code !== 0) { - reject(new Error(`Child process failed with exit-code ${code}`)); - } - resolve(); - }); - }); -} - -function preferLocalDependencies(command) { - const localCandidate = resolveLocalCandidate(command); - - if (fs.existsSync(localCandidate)) { - return localCandidate; - } - return command; -} - -function resolveLocalCandidate(command) { - if (process.platform === 'win32') { - return path.join('node_modules', '.bin', command + '.cmd'); - } - return path.join('node_modules', '.bin', command); -} diff --git a/tasks/util/git.js b/tasks/util/git.js index 46c9cbe7c..b73ca66ee 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -14,7 +14,7 @@ module.exports = { headSha, masterSha, tagName: await getTagName(), - isMaster: headSha === masterSha + isMaster: headSha === masterSha, }; }, async add(path) { @@ -23,7 +23,7 @@ module.exports = { async commit(message) { return git('commit', '--message', message); }, - git // visible for testing + git, // visible for testing }; async function getHeadSha() { @@ -52,7 +52,7 @@ async function getTagName() { } const tags = trimmedStdout.split(/\n|\r\n/); - const versionTags = tags.filter(tag => /^v/.test(tag)); + const versionTags = tags.filter((tag) => /^v/.test(tag)); if (versionTags[0] != null) { return versionTags[0]; } diff --git a/tasks/version.js b/tasks/version.js index ba89d1fd7..ac6bc9923 100644 --- a/tasks/version.js +++ b/tasks/version.js @@ -2,7 +2,7 @@ const git = require('./util/git'); const semver = require('semver'); const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); -module.exports = function(grunt) { +module.exports = function (grunt) { const registerAsyncTask = createRegisterAsyncTaskFn(grunt); registerAsyncTask('version', async () => { @@ -22,27 +22,27 @@ module.exports = function(grunt) { { path: 'lib/handlebars/base.js', regex: /const VERSION = ['"](.*)['"];/, - replacement: `const VERSION = '${version}';` + replacement: `const VERSION = '${version}';`, }, { path: 'components/bower.json', regex: /"version":.*/, - replacement: `"version": "${version}",` + replacement: `"version": "${version}",`, }, { path: 'components/package.json', regex: /"version":.*/, - replacement: `"version": "${version}",` + replacement: `"version": "${version}",`, }, { path: 'components/handlebars.js.nuspec', regex: /.*<\/version>/, - replacement: `${version}` - } + replacement: `${version}`, + }, ]; await Promise.all( - replaceSpec.map(replaceSpec => + replaceSpec.map((replaceSpec) => replaceAndAdd( replaceSpec.path, replaceSpec.regex, diff --git a/tests/bench/.eslintrc.js b/tests/bench/.eslintrc.js new file mode 100644 index 000000000..f882aff79 --- /dev/null +++ b/tests/bench/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + rules: { + 'no-console': 'off', + 'no-var': 'off', + }, +}; diff --git a/bench/dist-size.js b/tests/bench/dist-size.js similarity index 84% rename from bench/dist-size.js rename to tests/bench/dist-size.js index 5bbf7ee33..2b71a4c56 100644 --- a/bench/dist-size.js +++ b/tests/bench/dist-size.js @@ -2,13 +2,13 @@ var async = require('neo-async'), fs = require('fs'), zlib = require('zlib'); -module.exports = function(grunt, callback) { +module.exports = function (grunt, callback) { var distFiles = fs.readdirSync('dist'), distSizes = {}; async.each( distFiles, - function(file, callback) { + function (file, callback) { var content; try { content = fs.readFileSync('dist/' + file); @@ -24,7 +24,7 @@ module.exports = function(grunt, callback) { file = file.replace(/\.js/, '').replace(/\./g, '_'); distSizes[file] = content.length; - zlib.gzip(content, function(err, data) { + zlib.gzip(content, function (err, data) { if (err) { throw err; } @@ -33,7 +33,7 @@ module.exports = function(grunt, callback) { callback(); }); }, - function() { + function () { grunt.log.writeln( 'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2) ); diff --git a/bench/index.js b/tests/bench/index.js similarity index 88% rename from bench/index.js rename to tests/bench/index.js index 462b046f5..3f3f8702f 100644 --- a/bench/index.js +++ b/tests/bench/index.js @@ -1,7 +1,7 @@ var fs = require('fs'); var metrics = fs.readdirSync(__dirname); -metrics.forEach(function(metric) { +metrics.forEach(function (metric) { if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) { return; } diff --git a/bench/precompile-size.js b/tests/bench/precompile-size.js similarity index 78% rename from bench/precompile-size.js rename to tests/bench/precompile-size.js index 5aca9423c..64e10854a 100644 --- a/bench/precompile-size.js +++ b/tests/bench/precompile-size.js @@ -1,17 +1,17 @@ var _ = require('underscore'), templates = require('./templates'); -module.exports = function(grunt, callback) { +module.exports = function (grunt, callback) { // Deferring to here in case we have a build for parser, etc as part of this grunt exec - var Handlebars = require('../lib'); + var Handlebars = require('../../lib'); var templateSizes = {}; - _.each(templates, function(info, template) { + _.each(templates, function (info, template) { var src = info.handlebars, compiled = Handlebars.precompile(src, {}), knownHelpers = Handlebars.precompile(src, { knownHelpersOnly: true, - knownHelpers: info.helpers + knownHelpers: info.helpers, }); templateSizes[template] = compiled.length; diff --git a/bench/templates/arguments.js b/tests/bench/templates/arguments.js similarity index 73% rename from bench/templates/arguments.js rename to tests/bench/templates/arguments.js index f73d845f8..9f4e8c803 100644 --- a/bench/templates/arguments.js +++ b/tests/bench/templates/arguments.js @@ -1,13 +1,13 @@ module.exports = { helpers: { - foo: function() { + foo: function () { return ''; - } + }, }, context: { - bar: true + bar: true, }, handlebars: - '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}' + '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}', }; diff --git a/bench/templates/array-each.js b/tests/bench/templates/array-each.js similarity index 73% rename from bench/templates/array-each.js rename to tests/bench/templates/array-each.js index ddf07a6fe..a1d148edd 100644 --- a/bench/templates/array-each.js +++ b/tests/bench/templates/array-each.js @@ -4,11 +4,10 @@ module.exports = { { name: 'Moe' }, { name: 'Larry' }, { name: 'Curly' }, - { name: 'Shemp' } - ] + { name: 'Shemp' }, + ], }, handlebars: '{{#each names}}{{name}}{{/each}}', dust: '{#names}{name}{/names}', mustache: '{{#names}}{{name}}{{/names}}', - eco: '<% for item in @names: %><%= item.name %><% end %>' }; diff --git a/bench/templates/array-mustache.js b/tests/bench/templates/array-mustache.js similarity index 61% rename from bench/templates/array-mustache.js rename to tests/bench/templates/array-mustache.js index e7b2355ac..a1787c090 100644 --- a/bench/templates/array-mustache.js +++ b/tests/bench/templates/array-mustache.js @@ -4,8 +4,8 @@ module.exports = { { name: 'Moe' }, { name: 'Larry' }, { name: 'Curly' }, - { name: 'Shemp' } - ] + { name: 'Shemp' }, + ], }, - handlebars: '{{#names}}{{name}}{{/names}}' + handlebars: '{{#names}}{{name}}{{/names}}', }; diff --git a/bench/templates/complex.dust b/tests/bench/templates/complex.dust similarity index 100% rename from bench/templates/complex.dust rename to tests/bench/templates/complex.dust diff --git a/bench/templates/complex.handlebars b/tests/bench/templates/complex.handlebars similarity index 100% rename from bench/templates/complex.handlebars rename to tests/bench/templates/complex.handlebars diff --git a/bench/templates/complex.js b/tests/bench/templates/complex.js similarity index 77% rename from bench/templates/complex.js rename to tests/bench/templates/complex.js index 545a479cb..c10cef220 100644 --- a/bench/templates/complex.js +++ b/tests/bench/templates/complex.js @@ -2,19 +2,18 @@ var fs = require('fs'); module.exports = { context: { - header: function() { + header: function () { return 'Colors'; }, hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays items: [ { name: 'red', current: true, url: '#Red' }, { name: 'green', current: false, url: '#Green' }, - { name: 'blue', current: false, url: '#Blue' } - ] + { name: 'blue', current: false, url: '#Blue' }, + ], }, handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(), dust: fs.readFileSync(__dirname + '/complex.dust').toString(), - eco: fs.readFileSync(__dirname + '/complex.eco').toString(), - mustache: fs.readFileSync(__dirname + '/complex.mustache').toString() + mustache: fs.readFileSync(__dirname + '/complex.mustache').toString(), }; diff --git a/bench/templates/complex.mustache b/tests/bench/templates/complex.mustache similarity index 100% rename from bench/templates/complex.mustache rename to tests/bench/templates/complex.mustache diff --git a/bench/templates/data.js b/tests/bench/templates/data.js similarity index 57% rename from bench/templates/data.js rename to tests/bench/templates/data.js index 4cb969d66..97c2d5b67 100644 --- a/bench/templates/data.js +++ b/tests/bench/templates/data.js @@ -4,8 +4,8 @@ module.exports = { { name: 'Moe' }, { name: 'Larry' }, { name: 'Curly' }, - { name: 'Shemp' } - ] + { name: 'Shemp' }, + ], }, - handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}' + handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}', }; diff --git a/bench/templates/depth-1.js b/tests/bench/templates/depth-1.js similarity index 70% rename from bench/templates/depth-1.js rename to tests/bench/templates/depth-1.js index 65cb33b71..22551eeff 100644 --- a/bench/templates/depth-1.js +++ b/tests/bench/templates/depth-1.js @@ -4,11 +4,10 @@ module.exports = { { name: 'Moe' }, { name: 'Larry' }, { name: 'Curly' }, - { name: 'Shemp' } + { name: 'Shemp' }, ], - foo: 'bar' + foo: 'bar', }, handlebars: '{{#each names}}{{../foo}}{{/each}}', mustache: '{{#names}}{{foo}}{{/names}}', - eco: '<% for item in @names: %><%= @foo %><% end %>' }; diff --git a/bench/templates/depth-2.js b/tests/bench/templates/depth-2.js similarity index 67% rename from bench/templates/depth-2.js rename to tests/bench/templates/depth-2.js index bef804d1e..cf40afda8 100644 --- a/bench/templates/depth-2.js +++ b/tests/bench/templates/depth-2.js @@ -4,13 +4,11 @@ module.exports = { { bat: 'foo', name: ['Moe'] }, { bat: 'foo', name: ['Larry'] }, { bat: 'foo', name: ['Curly'] }, - { bat: 'foo', name: ['Shemp'] } + { bat: 'foo', name: ['Shemp'] }, ], - foo: 'bar' + foo: 'bar', }, handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}', mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}', - eco: - '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>' }; diff --git a/bench/templates/index.js b/tests/bench/templates/index.js similarity index 83% rename from bench/templates/index.js rename to tests/bench/templates/index.js index a718ea388..61feb5a3e 100644 --- a/bench/templates/index.js +++ b/tests/bench/templates/index.js @@ -1,7 +1,7 @@ var fs = require('fs'); var templates = fs.readdirSync(__dirname); -templates.forEach(function(template) { +templates.forEach(function (template) { if (template === 'index.js' || !/(.*)\.js$/.test(template)) { return; } diff --git a/bench/templates/object-mustache.js b/tests/bench/templates/object-mustache.js similarity index 57% rename from bench/templates/object-mustache.js rename to tests/bench/templates/object-mustache.js index 41774b73a..6db6101ed 100644 --- a/bench/templates/object-mustache.js +++ b/tests/bench/templates/object-mustache.js @@ -1,4 +1,4 @@ module.exports = { context: { person: { name: 'Larry', age: 45 } }, - handlebars: '{{#person}}{{name}}{{age}}{{/person}}' + handlebars: '{{#person}}{{name}}{{age}}{{/person}}', }; diff --git a/bench/templates/object.js b/tests/bench/templates/object.js similarity index 63% rename from bench/templates/object.js rename to tests/bench/templates/object.js index 084c070ad..2fcdc42e5 100644 --- a/bench/templates/object.js +++ b/tests/bench/templates/object.js @@ -2,6 +2,5 @@ module.exports = { context: { person: { name: 'Larry', age: 45 } }, handlebars: '{{#with person}}{{name}}{{age}}{{/with}}', dust: '{#person}{name}{age}{/person}', - eco: '<%= @person.name %><%= @person.age %>', - mustache: '{{#person}}{{name}}{{age}}{{/person}}' + mustache: '{{#person}}{{name}}{{age}}{{/person}}', }; diff --git a/bench/templates/partial-recursion.js b/tests/bench/templates/partial-recursion.js similarity index 83% rename from bench/templates/partial-recursion.js rename to tests/bench/templates/partial-recursion.js index ce3c32a74..fc18982e7 100644 --- a/bench/templates/partial-recursion.js +++ b/tests/bench/templates/partial-recursion.js @@ -1,13 +1,13 @@ module.exports = { context: { name: '1', - kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }] + kids: [{ name: '1.1', kids: [{ name: '1.1.1', kids: [] }] }], }, partials: { mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' }, - handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' } + handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }, }, handlebars: '{{name}}{{#each kids}}{{>recursion}}{{/each}}', dust: '{name}{#kids}{>recursion:./}{/kids}', - mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' + mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}', }; diff --git a/bench/templates/partial.js b/tests/bench/templates/partial.js similarity index 79% rename from bench/templates/partial.js rename to tests/bench/templates/partial.js index 79a9d7dfa..385673d4d 100644 --- a/bench/templates/partial.js +++ b/tests/bench/templates/partial.js @@ -3,17 +3,17 @@ module.exports = { peeps: [ { name: 'Moe', count: 15 }, { name: 'Larry', count: 5 }, - { name: 'Curly', count: 1 } - ] + { name: 'Curly', count: 1 }, + ], }, partials: { mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' }, handlebars: { - variables: 'Hello {{name}}! You have {{count}} new messages.' - } + variables: 'Hello {{name}}! You have {{count}} new messages.', + }, }, handlebars: '{{#each peeps}}{{>variables}}{{/each}}', dust: '{#peeps}{>variables/}{/peeps}', - mustache: '{{#peeps}}{{>variables}}{{/peeps}}' + mustache: '{{#peeps}}{{>variables}}{{/peeps}}', }; diff --git a/bench/templates/paths.js b/tests/bench/templates/paths.js similarity index 69% rename from bench/templates/paths.js rename to tests/bench/templates/paths.js index 3725e471c..85987dcdb 100644 --- a/bench/templates/paths.js +++ b/tests/bench/templates/paths.js @@ -3,7 +3,5 @@ module.exports = { handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}', dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}', - eco: - '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>', - mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}' + mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}', }; diff --git a/bench/templates/string.js b/tests/bench/templates/string.js similarity index 84% rename from bench/templates/string.js rename to tests/bench/templates/string.js index 6b0e94a74..104283bcd 100644 --- a/bench/templates/string.js +++ b/tests/bench/templates/string.js @@ -3,5 +3,4 @@ module.exports = { handlebars: 'Hello world', dust: 'Hello world', mustache: 'Hello world', - eco: 'Hello world' }; diff --git a/bench/templates/subexpression.js b/tests/bench/templates/subexpression.js similarity index 66% rename from bench/templates/subexpression.js rename to tests/bench/templates/subexpression.js index 659b53041..057eda00b 100644 --- a/bench/templates/subexpression.js +++ b/tests/bench/templates/subexpression.js @@ -1,14 +1,13 @@ module.exports = { helpers: { - echo: function(value) { + echo: function (value) { return 'foo ' + value; }, - header: function() { + header: function () { return 'Colors'; - } + }, }, handlebars: '{{echo (header)}}', - eco: '<%= @echo(@header()) %>' }; module.exports.context = module.exports.helpers; diff --git a/bench/templates/variables.js b/tests/bench/templates/variables.js similarity index 78% rename from bench/templates/variables.js rename to tests/bench/templates/variables.js index fddece1bc..c9a8dde30 100644 --- a/bench/templates/variables.js +++ b/tests/bench/templates/variables.js @@ -3,5 +3,4 @@ module.exports = { handlebars: 'Hello {{name}}! You have {{count}} new messages.', dust: 'Hello {name}! You have {count} new messages.', mustache: 'Hello {{name}}! You have {{count}} new messages.', - eco: 'Hello <%= @name %>! You have <%= @count %> new messages.' }; diff --git a/bench/throughput.js b/tests/bench/throughput.js similarity index 68% rename from bench/throughput.js rename to tests/bench/throughput.js index b5e3a56a5..b3591b683 100644 --- a/bench/throughput.js +++ b/tests/bench/throughput.js @@ -1,6 +1,5 @@ var _ = require('underscore'), runner = require('./util/template-runner'), - eco, dust, Handlebars, Mustache; @@ -17,12 +16,6 @@ try { /* NOP */ } -try { - eco = require('eco'); -} catch (err) { - /* NOP */ -} - function error() { throw new Error('EWOT'); } @@ -35,32 +28,31 @@ function makeSuite(bench, name, template, handlebarsOnly) { handlebarsOut, compatOut, dustOut, - ecoOut, mustacheOut; var handlebar = Handlebars.compile(template.handlebars, { data: false }), compat = Handlebars.compile(template.handlebars, { data: false, - compat: true + compat: true, }), options = { helpers: template.helpers }; - _.each(template.partials && template.partials.handlebars, function( - partial, - partialName - ) { - Handlebars.registerPartial( - partialName, - Handlebars.compile(partial, { data: false }) - ); - }); + _.each( + template.partials && template.partials.handlebars, + function (partial, partialName) { + Handlebars.registerPartial( + partialName, + Handlebars.compile(partial, { data: false }) + ); + } + ); handlebarsOut = handlebar(context, options); - bench('handlebars', function() { + bench('handlebars', function () { handlebar(context, options); }); compatOut = compat(context, options); - bench('compat', function() { + bench('compat', function () { compat(context, options); }); @@ -73,32 +65,18 @@ function makeSuite(bench, name, template, handlebarsOnly) { dustOut = false; dust.loadSource(dust.compile(template.dust, templateName)); - dust.render(templateName, context, function(err, out) { + dust.render(templateName, context, function (err, out) { dustOut = out; }); - bench('dust', function() { - dust.render(templateName, context, function() {}); + bench('dust', function () { + dust.render(templateName, context, function () {}); }); } else { bench('dust', error); } } - if (eco) { - if (template.eco) { - var ecoTemplate = eco.compile(template.eco); - - ecoOut = ecoTemplate(context); - - bench('eco', function() { - ecoTemplate(context); - }); - } else { - bench('eco', error); - } - } - if (Mustache) { var mustacheSource = template.mustache, mustachePartials = partials && partials.mustache; @@ -106,7 +84,7 @@ function makeSuite(bench, name, template, handlebarsOnly) { if (mustacheSource) { mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials); - bench('mustache', function() { + bench('mustache', function () { Mustache.to_html(mustacheSource, context, mustachePartials); }); } else { @@ -139,16 +117,15 @@ function makeSuite(bench, name, template, handlebarsOnly) { compare(compatOut, 'compat'); compare(dustOut, 'dust'); - compare(ecoOut, 'eco'); compare(mustacheOut, 'mustache'); } -module.exports = function(grunt, callback) { - // Deferring load incase we are being run inline with the grunt build - Handlebars = require('../lib'); +module.exports = function (grunt, callback) { + // Deferring load in case we are being run inline with the grunt build + Handlebars = require('../../lib'); console.log('Execution Throughput'); - runner(grunt, makeSuite, function(times, scaled) { + runner(grunt, makeSuite, function (times, scaled) { callback(scaled); }); }; diff --git a/bench/util/benchwarmer.js b/tests/bench/util/benchwarmer.js similarity index 74% rename from bench/util/benchwarmer.js rename to tests/bench/util/benchwarmer.js index 90415bf56..25d881cf9 100644 --- a/bench/util/benchwarmer.js +++ b/tests/bench/util/benchwarmer.js @@ -11,24 +11,22 @@ function BenchWarmer() { this.errors = {}; } -var print = require('util').print; - BenchWarmer.prototype = { - winners: function(benches) { + winners: function (benches) { return Benchmark.filter(benches, 'fastest'); }, - suite: function(suite, fn) { + suite: function (suite, fn) { this.suiteName = suite; this.times[suite] = {}; this.first = true; var self = this; - fn(function(name, benchFn) { + fn(function (name, benchFn) { self.push(name, benchFn); }); }, - push: function(name, fn) { + push: function (name, fn) { if (this.names.indexOf(name) === -1) { this.names.push(name); } @@ -40,16 +38,16 @@ BenchWarmer.prototype = { var bench = new Benchmark(fn, { name: this.suiteName + ': ' + name, - onComplete: function() { + onComplete: function () { if (first) { self.startLine(suiteName); } self.writeBench(bench); self.currentBenches.push(bench); }, - onError: function() { + onError: function () { self.errors[this.name] = this; - } + }, }); bench.suiteName = this.suiteName; bench.benchName = name; @@ -57,28 +55,28 @@ BenchWarmer.prototype = { this.benchmarks.push(bench); }, - bench: function(callback) { + bench: function (callback) { var self = this; this.printHeader('ops/msec', true); Benchmark.invoke(this.benchmarks, { name: 'run', - onComplete: function() { + onComplete: function () { self.scaleTimes(); self.startLine(''); - print('\n'); + console.log('\n'); self.printHeader('scaled'); - _.each(self.scaled, function(value, name) { + _.each(self.scaled, function (value, name) { self.startLine(name); - _.each(self.names, function(lang) { + _.each(self.names, function (lang) { self.writeValue(value[lang] || ''); }); }); - print('\n'); + console.log('\n'); var errors = false, prop, @@ -94,37 +92,37 @@ BenchWarmer.prototype = { } if (errors) { - print('\n\nErrors:\n'); - Object.keys(self.errors).forEach(function(prop) { + console.log('\n\nErrors:\n'); + Object.keys(self.errors).forEach(function (prop) { if (self.errors[prop].error.message !== 'EWOT') { bench = self.errors[prop]; - print('\n' + bench.name + ':\n'); - print(bench.error.message); + console.log('\n' + bench.name + ':\n'); + console.log(bench.error.message); if (bench.error.stack) { - print(bench.error.stack.join('\n')); + console.log(bench.error.stack.join('\n')); } - print('\n'); + console.log('\n'); } }); } callback(); - } + }, }); - print('\n'); + console.log('\n'); }, - scaleTimes: function() { + scaleTimes: function () { var scaled = (this.scaled = {}); _.each( this.times, - function(times, name) { + function (times, name) { var output = (scaled[name] = {}); _.each( times, - function(time, lang) { + function (time, lang) { output[lang] = ( ((time - this.minimum) / (this.maximum - this.minimum)) * 100 @@ -137,7 +135,7 @@ BenchWarmer.prototype = { ); }, - printHeader: function(title, winners) { + printHeader: function (title, winners) { var benchSize = 0, names = this.names, i, @@ -163,30 +161,31 @@ BenchWarmer.prototype = { } if (winners) { - print('WINNER(S)'); + console.log('WINNER(S)'); horSize = horSize + 'WINNER(S)'.length; } - print('\n' + new Array(horSize + 1).join('-')); + console.log('\n' + new Array(horSize + 1).join('-')); }, - startLine: function(name) { - var winners = Benchmark.map(this.winners(this.currentBenches), function( - bench - ) { - return bench.name.split(': ')[1]; - }); + startLine: function (name) { + var winners = Benchmark.map( + this.winners(this.currentBenches), + function (bench) { + return bench.name.split(': ')[1]; + } + ); this.currentBenches = []; - print(winners.join(', ')); - print('\n'); + console.log(winners.join(', ')); + console.log('\n'); if (name) { this.writeValue(name); } }, - writeBench: function(bench) { + writeBench: function (bench) { var out; if (!bench.error) { @@ -213,11 +212,11 @@ BenchWarmer.prototype = { this.writeValue(out); }, - writeValue: function(out) { + writeValue: function (out) { var padding = this.benchSize - out.length + 1; out = out + new Array(padding).join(' '); - print(out); - } + console.log(out); + }, }; module.exports = BenchWarmer; diff --git a/bench/util/template-runner.js b/tests/bench/util/template-runner.js similarity index 74% rename from bench/util/template-runner.js rename to tests/bench/util/template-runner.js index 370e157ec..9112c2ef2 100644 --- a/bench/util/template-runner.js +++ b/tests/bench/util/template-runner.js @@ -2,7 +2,7 @@ var _ = require('underscore'), BenchWarmer = require('./benchwarmer'), templates = require('../templates'); -module.exports = function(grunt, makeSuite, callback) { +module.exports = function (grunt, makeSuite, callback) { var warmer = new BenchWarmer(); var handlebarsOnly = grunt.option('handlebars-only'), @@ -11,17 +11,17 @@ module.exports = function(grunt, makeSuite, callback) { grep = new RegExp(grep); } - _.each(templates, function(template, name) { + _.each(templates, function (template, name) { if (!template.handlebars || (grep && !grep.test(name))) { return; } - warmer.suite(name, function(bench) { + warmer.suite(name, function (bench) { makeSuite(bench, name, template, handlebarsOnly); }); }); - warmer.bench(function() { + warmer.bench(function () { if (callback) { callback(warmer.times, warmer.scaled); } diff --git a/tests/browser/.eslintrc.js b/tests/browser/.eslintrc.js new file mode 100644 index 000000000..81f535a8a --- /dev/null +++ b/tests/browser/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + parserOptions: { + ecmaVersion: 2018, + }, +}; diff --git a/tests/browser/README.md b/tests/browser/README.md new file mode 100644 index 000000000..b35a5a2b6 --- /dev/null +++ b/tests/browser/README.md @@ -0,0 +1,14 @@ +# Browser Tests with Playwright + +These tests execute Mocha tests from the `spec`-folder in multiple browsers. + +## Using Docker + +Execute the following commands in the project root: + +```bash +npm install +npx grunt prepare +docker pull mcr.microsoft.com/playwright:focal +docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:focal npm run test:browser +``` \ No newline at end of file diff --git a/tests/browser/playwright.config.js b/tests/browser/playwright.config.js new file mode 100644 index 000000000..dda5c51a4 --- /dev/null +++ b/tests/browser/playwright.config.js @@ -0,0 +1,27 @@ +const { devices } = require('@playwright/test'); + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +const config = { + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], + reporter: 'list', + webServer: { + command: 'npm run test:serve', + port: 9999, + reuseExistingServer: false, + }, +}; + +module.exports = config; diff --git a/tests/browser/spec.js b/tests/browser/spec.js new file mode 100644 index 000000000..dc0762e9b --- /dev/null +++ b/tests/browser/spec.js @@ -0,0 +1,23 @@ +const { test, expect } = require('@playwright/test'); + +async function waitForMochaAndAssertResult(page) { + await page.waitForFunction(() => window.mochaResults); // eslint-disable-line no-undef + const mochaResults = await page.evaluate('window.mochaResults'); + + expect(mochaResults.failures).toBe(0); +} + +test('Spec handlebars.js', async ({ page, baseURL }) => { + await page.goto(`${baseURL}/spec/?headless=true`); + await waitForMochaAndAssertResult(page); +}); + +test('Spec handlebars.js (UMD)', async ({ page, baseURL }) => { + await page.goto(`${baseURL}/spec/umd.html?headless=true`); + await waitForMochaAndAssertResult(page); +}); + +test('Spec handlebars.runtime.js (UMD)', async ({ page, baseURL }) => { + await page.goto(`${baseURL}/spec/umd-runtime.html?headless=true`); + await waitForMochaAndAssertResult(page); +}); diff --git a/integration-testing/README.md b/tests/integration/README.md similarity index 55% rename from integration-testing/README.md rename to tests/integration/README.md index bd9554c53..7dcde29e8 100644 --- a/integration-testing/README.md +++ b/tests/integration/README.md @@ -1,12 +1,10 @@ Add a new integration test by creating a new subfolder -Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code +Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code and display an error message, if something goes wrong. -* An integration test should reflect real-world setups that use handlebars. -* It should compile a minimal template and compare the output to an expected output. -* It should use "../.." as dependency for Handlebars so that the currently built library is used. +- An integration test should reflect real-world setups that use handlebars. +- It should compile a minimal template and compare the output to an expected output. +- It should use "../.." as dependency for Handlebars so that the currently built library is used. -Currently, integration tests are only running on Linux, especially in travis-ci. - - \ No newline at end of file +Currently, integration tests are only running on Linux, especially in our CI GitHub action. diff --git a/tests/integration/multi-nodejs-test/.eslintrc.js b/tests/integration/multi-nodejs-test/.eslintrc.js new file mode 100644 index 000000000..f882aff79 --- /dev/null +++ b/tests/integration/multi-nodejs-test/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + rules: { + 'no-console': 'off', + 'no-var': 'off', + }, +}; diff --git a/integration-testing/multi-nodejs-test/.gitignore b/tests/integration/multi-nodejs-test/.gitignore similarity index 100% rename from integration-testing/multi-nodejs-test/.gitignore rename to tests/integration/multi-nodejs-test/.gitignore diff --git a/integration-testing/multi-nodejs-test/package.json b/tests/integration/multi-nodejs-test/package.json similarity index 91% rename from integration-testing/multi-nodejs-test/package.json rename to tests/integration/multi-nodejs-test/package.json index 2fac51d2d..6002c0ce2 100644 --- a/integration-testing/multi-nodejs-test/package.json +++ b/tests/integration/multi-nodejs-test/package.json @@ -7,7 +7,7 @@ "private": true, "license": "MIT", "dependencies": { - "handlebars": "file:../.." + "handlebars": "file:../../.." }, "scripts": { "test": "node run-handlebars.js", diff --git a/integration-testing/multi-nodejs-test/precompile-test-template.txt.hbs b/tests/integration/multi-nodejs-test/precompile-test-template.txt.hbs similarity index 100% rename from integration-testing/multi-nodejs-test/precompile-test-template.txt.hbs rename to tests/integration/multi-nodejs-test/precompile-test-template.txt.hbs diff --git a/integration-testing/multi-nodejs-test/run-handlebars.js b/tests/integration/multi-nodejs-test/run-handlebars.js similarity index 100% rename from integration-testing/multi-nodejs-test/run-handlebars.js rename to tests/integration/multi-nodejs-test/run-handlebars.js diff --git a/integration-testing/multi-nodejs-test/test.sh b/tests/integration/multi-nodejs-test/test.sh similarity index 76% rename from integration-testing/multi-nodejs-test/test.sh rename to tests/integration/multi-nodejs-test/test.sh index d16361dfb..f162118db 100755 --- a/integration-testing/multi-nodejs-test/test.sh +++ b/tests/integration/multi-nodejs-test/test.sh @@ -7,9 +7,6 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This script tests with precompiler and the built distribution with multiple NodeJS version. -# The rest of the travis-build will only work with newer NodeJS versions, because the build -# tools don't support older versions. -# However, the built distribution should work with older NodeJS versions as well. # This test is simple by design. It merely ensures, that calling Handlebars does not fail with old versions. # It does (almost) not test for correctness, because that is already done in the mocha-tests. # And it does not use any NodeJS based testing framework to make this part independent of the Node version. @@ -17,7 +14,7 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1 unset npm_config_prefix echo "Handlebars should be able to run in various versions of NodeJS" -for node_version_to_test in 0.10 0.12 4 5 6 7 8 9 10 11 12 13 14 15; do +for node_version_to_test in 12 14 16 18; do rm target node_modules package-lock.json -rf mkdir target diff --git a/integration-testing/webpack-babel-test/.gitignore b/tests/integration/rollup-test/.gitignore similarity index 100% rename from integration-testing/webpack-babel-test/.gitignore rename to tests/integration/rollup-test/.gitignore diff --git a/tests/integration/rollup-test/package.json b/tests/integration/rollup-test/package.json new file mode 100644 index 000000000..dd0a735dd --- /dev/null +++ b/tests/integration/rollup-test/package.json @@ -0,0 +1,14 @@ +{ + "name": "rollup-test", + "description": "Various tests with Handlebars and rollup", + "version": "1.0.0", + "scripts": { + "build": "rollup --config rollup.config.js" + }, + "private": true, + "devDependencies": { + "@rollup/plugin-node-resolve": "^13.1.1", + "handlebars": "file:../../..", + "rollup": "^2.61.1" + } +} diff --git a/tests/integration/rollup-test/rollup.config.js b/tests/integration/rollup-test/rollup.config.js new file mode 100644 index 000000000..809de8a73 --- /dev/null +++ b/tests/integration/rollup-test/rollup.config.js @@ -0,0 +1,10 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +export default { + input: 'src/index.js', + output: { + file: 'dist/bundle.js', + format: 'es', + }, + plugins: [nodeResolve()], +}; diff --git a/tests/integration/rollup-test/src/index.js b/tests/integration/rollup-test/src/index.js new file mode 100644 index 000000000..2929870d9 --- /dev/null +++ b/tests/integration/rollup-test/src/index.js @@ -0,0 +1,8 @@ +import Handlebars from 'handlebars/lib/handlebars'; + +const template = Handlebars.compile('Author: {{author}}'); +const result = template({ author: 'Yehuda' }); + +if (result !== 'Author: Yehuda') { + throw Error('Assertion failed'); +} diff --git a/tests/integration/rollup-test/test.sh b/tests/integration/rollup-test/test.sh new file mode 100755 index 000000000..dabf8043e --- /dev/null +++ b/tests/integration/rollup-test/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +# Cleanup: package-lock and "npm ci" is not working with local dependencies +rm dist package-lock.json -rf +npm install +npm run build + +node dist/bundle.js +echo "Success" \ No newline at end of file diff --git a/integration-testing/run-integration-tests.sh b/tests/integration/run-integration-tests.sh similarity index 100% rename from integration-testing/run-integration-tests.sh rename to tests/integration/run-integration-tests.sh diff --git a/integration-testing/webpack-babel-test/.babelrc b/tests/integration/webpack-babel-test/.babelrc similarity index 100% rename from integration-testing/webpack-babel-test/.babelrc rename to tests/integration/webpack-babel-test/.babelrc diff --git a/integration-testing/webpack-test/.gitignore b/tests/integration/webpack-babel-test/.gitignore similarity index 100% rename from integration-testing/webpack-test/.gitignore rename to tests/integration/webpack-babel-test/.gitignore diff --git a/integration-testing/webpack-babel-test/package.json b/tests/integration/webpack-babel-test/package.json similarity index 62% rename from integration-testing/webpack-babel-test/package.json rename to tests/integration/webpack-babel-test/package.json index dd21ae0af..d071551cc 100644 --- a/integration-testing/webpack-babel-test/package.json +++ b/tests/integration/webpack-babel-test/package.json @@ -1,24 +1,21 @@ { "name": "webpack-babel-test", - "version": "1.0.0", "description": "", - "main": "index.js", - "keywords": [], - "author": "", - "license": "ISC", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "webpack --config webpack.config.js" + }, "dependencies": { "@babel/core": "^7.5.5", "@babel/preset-env": "^7.5.5", "@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1", "babel-loader": "^8.0.6", - "babel-plugin-istanbul": "^5.2.0", - "handlebars": "file:../..", + "babel-plugin-istanbul": "^6.1.1", + "handlebars": "file:../../..", "handlebars-loader": "^1.7.1", - "nyc": "^14.1.1", - "webpack": "^4.39.3", - "webpack-cli": "^3.3.7" - }, - "scripts": { - "build": "webpack --config webpack.config.js" + "nyc": "^15.1.0", + "webpack": "^5.72.1", + "webpack-cli": "^4.9.2" } } diff --git a/tests/integration/webpack-babel-test/src/.eslintrc.js b/tests/integration/webpack-babel-test/src/.eslintrc.js new file mode 100644 index 000000000..fe68bfd9a --- /dev/null +++ b/tests/integration/webpack-babel-test/src/.eslintrc.js @@ -0,0 +1,12 @@ +/* eslint-env node */ +module.exports = { + root: true, + extends: ['eslint:recommended', 'prettier'], + env: { + browser: true, + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 6, + }, +}; diff --git a/integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js b/tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js similarity index 85% rename from integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js rename to tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js index 2d54945c8..0fdfb0d1d 100644 --- a/integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js +++ b/tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js @@ -2,7 +2,7 @@ import * as Handlebars from 'handlebars/runtime'; import hbs from 'handlebars-inline-precompile'; import { assertEquals } from '../../webpack-test/src/lib/assert'; -Handlebars.registerHelper('loud', function(text) { +Handlebars.registerHelper('loud', function (text) { return text.toUpperCase(); }); diff --git a/integration-testing/webpack-babel-test/src/lib/assert.js b/tests/integration/webpack-babel-test/src/lib/assert.js similarity index 100% rename from integration-testing/webpack-babel-test/src/lib/assert.js rename to tests/integration/webpack-babel-test/src/lib/assert.js diff --git a/integration-testing/webpack-babel-test/test.sh b/tests/integration/webpack-babel-test/test.sh similarity index 89% rename from integration-testing/webpack-babel-test/test.sh rename to tests/integration/webpack-babel-test/test.sh index a45d5625f..f473863ae 100755 --- a/integration-testing/webpack-babel-test/test.sh +++ b/tests/integration/webpack-babel-test/test.sh @@ -4,7 +4,7 @@ set -e # Cleanup: package-lock and "npm ci" is not working with local dependencies rm dist package-lock.json -rf -npm install +npm install --legacy-peer-deps npm run build for i in dist/*-test.js ; do diff --git a/integration-testing/webpack-babel-test/webpack.config.js b/tests/integration/webpack-babel-test/webpack.config.js similarity index 62% rename from integration-testing/webpack-babel-test/webpack.config.js rename to tests/integration/webpack-babel-test/webpack.config.js index bebb6ee0e..2c24b2c99 100644 --- a/integration-testing/webpack-babel-test/webpack.config.js +++ b/tests/integration/webpack-babel-test/webpack.config.js @@ -3,16 +3,18 @@ const fs = require('fs'); const testFiles = fs.readdirSync('src'); const entryPoints = {}; testFiles - .filter(file => file.match(/-test.js$/)) - .forEach(file => { + .filter((file) => file.match(/-test.js$/)) + .forEach((file) => { entryPoints[file] = `./src/${file}`; }); module.exports = { entry: entryPoints, + mode: 'production', + target: 'web', output: { filename: '[name]', - path: __dirname + '/dist' + path: __dirname + '/dist', }, module: { rules: [ @@ -21,12 +23,12 @@ module.exports = { exclude: /node_modules/, use: { loader: 'babel-loader', - options: { cacheDirectory: false } - } - } - ] + options: { cacheDirectory: false }, + }, + }, + ], }, optimization: { - minimize: false - } + minimize: false, + }, }; diff --git a/tests/integration/webpack-test/.gitignore b/tests/integration/webpack-test/.gitignore new file mode 100644 index 000000000..3a8ec2b3f --- /dev/null +++ b/tests/integration/webpack-test/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +package-lock.json \ No newline at end of file diff --git a/tests/integration/webpack-test/package.json b/tests/integration/webpack-test/package.json new file mode 100644 index 000000000..de27cc64a --- /dev/null +++ b/tests/integration/webpack-test/package.json @@ -0,0 +1,15 @@ +{ + "name": "webpack-test", + "description": "Various tests with Handlebars and multiple webpack versions", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "webpack --config webpack.config.js" + }, + "dependencies": { + "handlebars": "file:../../..", + "handlebars-loader": "^1.7.1", + "webpack": "^5.72.1", + "webpack-cli": "^4.9.2" + } +} diff --git a/tests/integration/webpack-test/src/.eslintrc.js b/tests/integration/webpack-test/src/.eslintrc.js new file mode 100644 index 000000000..b3c652549 --- /dev/null +++ b/tests/integration/webpack-test/src/.eslintrc.js @@ -0,0 +1,12 @@ +module.exports = { + root: true, + extends: ['eslint:recommended', 'prettier'], + env: { + node: true, + browser: true, + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 6, + }, +}; diff --git a/integration-testing/webpack-test/src/handlebars-default-import-pre-4.2-test.js b/tests/integration/webpack-test/src/handlebars-default-import-pre-4.2-test.js similarity index 100% rename from integration-testing/webpack-test/src/handlebars-default-import-pre-4.2-test.js rename to tests/integration/webpack-test/src/handlebars-default-import-pre-4.2-test.js diff --git a/integration-testing/webpack-test/src/handlebars-default-import-test.js b/tests/integration/webpack-test/src/handlebars-default-import-test.js similarity index 100% rename from integration-testing/webpack-test/src/handlebars-default-import-test.js rename to tests/integration/webpack-test/src/handlebars-default-import-test.js diff --git a/tests/integration/webpack-test/src/handlebars-esm-import-test.js b/tests/integration/webpack-test/src/handlebars-esm-import-test.js new file mode 100644 index 000000000..c11473844 --- /dev/null +++ b/tests/integration/webpack-test/src/handlebars-esm-import-test.js @@ -0,0 +1,5 @@ +import Handlebars from 'handlebars/lib/handlebars'; +import { assertEquals } from './lib/assert'; + +const template = Handlebars.compile('Author: {{author}}'); +assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda'); diff --git a/integration-testing/webpack-test/src/handlebars-loader-test.js b/tests/integration/webpack-test/src/handlebars-loader-test.js similarity index 100% rename from integration-testing/webpack-test/src/handlebars-loader-test.js rename to tests/integration/webpack-test/src/handlebars-loader-test.js diff --git a/integration-testing/webpack-test/src/handlebars-require-vs-import-test.js b/tests/integration/webpack-test/src/handlebars-require-vs-import-test.js similarity index 84% rename from integration-testing/webpack-test/src/handlebars-require-vs-import-test.js rename to tests/integration/webpack-test/src/handlebars-require-vs-import-test.js index 9c49f0cbd..ed75fd963 100644 --- a/integration-testing/webpack-test/src/handlebars-require-vs-import-test.js +++ b/tests/integration/webpack-test/src/handlebars-require-vs-import-test.js @@ -2,7 +2,7 @@ import * as HandlebarsViaImport from 'handlebars'; const HandlebarsViaRequire = require('handlebars'); import { assertEquals } from './lib/assert'; -HandlebarsViaImport.registerHelper('loud', function(text) { +HandlebarsViaImport.registerHelper('loud', function (text) { return text.toUpperCase(); }); diff --git a/tests/integration/webpack-test/src/handlebars-runtime-test.js b/tests/integration/webpack-test/src/handlebars-runtime-test.js new file mode 100644 index 000000000..d9250ecd5 --- /dev/null +++ b/tests/integration/webpack-test/src/handlebars-runtime-test.js @@ -0,0 +1,43 @@ +import * as Handlebars from 'handlebars/runtime'; +import { assertEquals } from './lib/assert'; + +const template = Handlebars.template({ + compiler: [8, '>= 4.3.0'], + main: function (container, depth0, helpers, partials, data) { + var helper, + lookupProperty = + container.lookupProperty || + function (parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined; + }; + + return ( + 'Author: ' + + container.escapeExpression( + ((helper = + (helper = + lookupProperty(helpers, 'author') || + (depth0 != null ? lookupProperty(depth0, 'author') : depth0)) != + null + ? helper + : container.hooks.helperMissing), + typeof helper === 'function' + ? helper.call(depth0 != null ? depth0 : container.nullContext || {}, { + name: 'author', + hash: {}, + data: data, + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 18 }, + }, + }) + : helper) + ) + ); + }, + useData: true, +}); +assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda'); diff --git a/integration-testing/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js b/tests/integration/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js similarity index 100% rename from integration-testing/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js rename to tests/integration/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js diff --git a/integration-testing/webpack-test/src/handlebars-wildcard-import-test.js b/tests/integration/webpack-test/src/handlebars-wildcard-import-test.js similarity index 100% rename from integration-testing/webpack-test/src/handlebars-wildcard-import-test.js rename to tests/integration/webpack-test/src/handlebars-wildcard-import-test.js diff --git a/integration-testing/webpack-test/src/lib/assert.js b/tests/integration/webpack-test/src/lib/assert.js similarity index 100% rename from integration-testing/webpack-test/src/lib/assert.js rename to tests/integration/webpack-test/src/lib/assert.js diff --git a/integration-testing/webpack-test/src/test-template.handlebars b/tests/integration/webpack-test/src/test-template.handlebars similarity index 100% rename from integration-testing/webpack-test/src/test-template.handlebars rename to tests/integration/webpack-test/src/test-template.handlebars diff --git a/integration-testing/webpack-test/test.sh b/tests/integration/webpack-test/test.sh similarity index 89% rename from integration-testing/webpack-test/test.sh rename to tests/integration/webpack-test/test.sh index a45d5625f..f473863ae 100755 --- a/integration-testing/webpack-test/test.sh +++ b/tests/integration/webpack-test/test.sh @@ -4,7 +4,7 @@ set -e # Cleanup: package-lock and "npm ci" is not working with local dependencies rm dist package-lock.json -rf -npm install +npm install --legacy-peer-deps npm run build for i in dist/*-test.js ; do diff --git a/integration-testing/webpack-test/webpack.config.js b/tests/integration/webpack-test/webpack.config.js similarity index 67% rename from integration-testing/webpack-test/webpack.config.js rename to tests/integration/webpack-test/webpack.config.js index 207aed30c..96e728eca 100644 --- a/integration-testing/webpack-test/webpack.config.js +++ b/tests/integration/webpack-test/webpack.config.js @@ -3,18 +3,20 @@ const fs = require('fs'); const testFiles = fs.readdirSync('src'); const entryPoints = {}; testFiles - .filter(file => file.match(/-test.js$/)) - .forEach(file => { + .filter((file) => file.match(/-test.js$/)) + .forEach((file) => { entryPoints[file] = `./src/${file}`; }); module.exports = { entry: entryPoints, + mode: 'production', + target: 'web', output: { filename: '[name]', - path: __dirname + '/dist' + path: __dirname + '/dist', }, module: { - rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }] - } + rules: [{ test: /\.handlebars$/, loader: 'handlebars-loader' }], + }, }; diff --git a/tests/print-script.js b/tests/print-script.js new file mode 100755 index 000000000..c8d9ddbb3 --- /dev/null +++ b/tests/print-script.js @@ -0,0 +1,114 @@ +/* eslint-disable no-console, no-var */ +// Util script for debugging source code generation issues + +var script = process.argv[2].replace(/\\n/g, '\n'), + verbose = process.argv[3] === '-v'; + +var Handlebars = require('./../lib'), + SourceMap = require('source-map'), + SourceMapConsumer = SourceMap.SourceMapConsumer; + +var template = Handlebars.precompile(script, { + srcName: 'input.hbs', + destName: 'output.js', + + assumeObjects: true, + compat: false, + strict: true, + trackIds: true, + knownHelpersOnly: false, +}); + +if (!verbose) { + console.log(template); +} else { + var consumer = new SourceMapConsumer(template.map), + lines = template.code.split('\n'), + srcLines = script.split('\n'); + + console.log(); + console.log('Source:'); + srcLines.forEach(function (source, index) { + console.log(index + 1, source); + }); + console.log(); + console.log('Generated:'); + console.log(template.code); + lines.forEach(function (source, index) { + console.log(index + 1, source); + }); + console.log(); + console.log('Map:'); + console.log(template.map); + console.log(); + + // eslint-disable-next-line no-inner-declarations + function collectSource(lines, lineName, colName, order) { + var ret = {}, + ordered = [], + last; + + function collect(current) { + if (last) { + var mapLines = lines.slice( + last[lineName] - 1, + current && current[lineName] + ); + if (mapLines.length) { + if (current) { + mapLines[mapLines.length - 1] = mapLines[mapLines.length - 1].slice( + 0, + current[colName] + ); + } + mapLines[0] = mapLines[0].slice(last[colName]); + } + ret[last[lineName] + ':' + last[colName]] = mapLines.join('\n'); + ordered.push({ + startLine: last[lineName], + startCol: last[colName], + endLine: current && current[lineName], + }); + } + last = current; + } + + consumer.eachMapping(collect, undefined, order); + collect(); + + return ret; + } + + srcLines = collectSource( + srcLines, + 'originalLine', + 'originalColumn', + SourceMapConsumer.ORIGINAL_ORDER + ); + lines = collectSource(lines, 'generatedLine', 'generatedColumn'); + + consumer.eachMapping(function (mapping) { + var originalSrc = + srcLines[mapping.originalLine + ':' + mapping.originalColumn], + generatedSrc = + lines[mapping.generatedLine + ':' + mapping.generatedColumn]; + + if (!mapping.originalLine) { + console.log( + 'generated', + mapping.generatedLine + ':' + mapping.generatedColumn, + generatedSrc + ); + } else { + console.log( + 'map', + mapping.source, + mapping.originalLine + ':' + mapping.originalColumn, + originalSrc, + '->', + mapping.generatedLine + ':' + mapping.generatedColumn, + generatedSrc + ); + } + }); +} diff --git a/types/index.d.ts b/types/index.d.ts index 3f2f8b792..4275c50f4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,6 +13,12 @@ * https://github.com/DefinitelyTyped/DefinitelyTyped/commits/1ce60bdc07f10e0b076778c6c953271c072bc894/types/handlebars/index.d.ts */ // TypeScript Version: 2.3 +import { + parse, + parseWithoutProcessing, + ParseOptions, + AST +} from '@handlebars/parser'; declare namespace Handlebars { export interface TemplateDelegate { @@ -50,10 +56,7 @@ declare namespace Handlebars { [key: string]: HelperDelegate; } - export interface ParseOptions { - srcName?: string; - ignoreStandalone?: boolean; - } + export { parse, parseWithoutProcessing, ParseOptions }; export function registerHelper(name: string, fn: HelperDelegate): void; export function registerHelper(name: HelperDeclareSpec): void; @@ -71,8 +74,7 @@ declare namespace Handlebars { export function createFrame(object: any): any; export function blockParams(obj: any[], ids: any[]): any[]; export function log(level: number, obj: any): void; - export function parse(input: string, options?: ParseOptions): hbs.AST.Program; - export function parseWithoutProcessing(input: string, options?: ParseOptions): hbs.AST.Program; + export function compile(input: any, options?: CompileOptions): HandlebarsTemplateDelegate; export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification; export function template(precompilation: TemplateSpecification): HandlebarsTemplateDelegate; @@ -127,7 +129,7 @@ declare namespace Handlebars { export const helpers: hbs.AST.helpers; } - interface ICompiler { + export interface ICompiler { accept(node: hbs.AST.Node): void; Program(program: hbs.AST.Program): void; BlockStatement(block: hbs.AST.BlockStatement): void; @@ -191,25 +193,25 @@ declare namespace Handlebars { /** * Implement this interface on your MVW/MVVM/MVC views such as Backbone.View **/ -interface HandlebarsTemplatable { +export interface HandlebarsTemplatable { template: HandlebarsTemplateDelegate; } // NOTE: for backward compatibility of this typing -type HandlebarsTemplateDelegate = Handlebars.TemplateDelegate; +export type HandlebarsTemplateDelegate = Handlebars.TemplateDelegate; -interface HandlebarsTemplates { +export interface HandlebarsTemplates { [index: string]: HandlebarsTemplateDelegate; } -interface TemplateSpecification { +export interface TemplateSpecification { } // for backward compatibility of this typing -type RuntimeOptions = Handlebars.RuntimeOptions; +export type RuntimeOptions = Handlebars.RuntimeOptions; -interface CompileOptions { +export interface CompileOptions { data?: boolean; compat?: boolean; knownHelpers?: KnownHelpers; @@ -222,11 +224,11 @@ interface CompileOptions { explicitPartialContext?: boolean; } -type KnownHelpers = { +export type KnownHelpers = { [name in BuiltinHelperName | CustomHelperName]: boolean; }; -type BuiltinHelperName = +export type BuiltinHelperName = "helperMissing"| "blockHelperMissing"| "each"| @@ -236,21 +238,23 @@ type BuiltinHelperName = "log"| "lookup"; -type CustomHelperName = string; +export type CustomHelperName = string; -interface PrecompileOptions extends CompileOptions { +export interface PrecompileOptions extends CompileOptions { srcName?: string; destName?: string; } -declare namespace hbs { +export namespace hbs { // for backward compatibility of this typing - type SafeString = Handlebars.SafeString; + export type SafeString = Handlebars.SafeString; + + export type Utils = typeof Handlebars.Utils; - type Utils = typeof Handlebars.Utils; + export { AST } } -interface Logger { +export interface Logger { DEBUG: number; INFO: number; WARN: number; @@ -262,161 +266,10 @@ interface Logger { log(level: number, obj: string): void; } -type CompilerInfo = [number/* revision */, string /* versions */]; - -declare namespace hbs { - namespace AST { - interface Node { - type: string; - loc: SourceLocation; - } - - interface SourceLocation { - source: string; - start: Position; - end: Position; - } - - interface Position { - line: number; - column: number; - } - - interface Program extends Node { - body: Statement[]; - blockParams: string[]; - } - - interface Statement extends Node {} - - interface MustacheStatement extends Statement { - type: 'MustacheStatement'; - path: PathExpression | Literal; - params: Expression[]; - hash: Hash; - escaped: boolean; - strip: StripFlags; - } - - interface Decorator extends MustacheStatement { } - - interface BlockStatement extends Statement { - type: 'BlockStatement'; - path: PathExpression; - params: Expression[]; - hash: Hash; - program: Program; - inverse: Program; - openStrip: StripFlags; - inverseStrip: StripFlags; - closeStrip: StripFlags; - } - - interface DecoratorBlock extends BlockStatement { } - - interface PartialStatement extends Statement { - type: 'PartialStatement'; - name: PathExpression | SubExpression; - params: Expression[]; - hash: Hash; - indent: string; - strip: StripFlags; - } - - interface PartialBlockStatement extends Statement { - type: 'PartialBlockStatement'; - name: PathExpression | SubExpression; - params: Expression[]; - hash: Hash; - program: Program; - openStrip: StripFlags; - closeStrip: StripFlags; - } - - interface ContentStatement extends Statement { - type: 'ContentStatement'; - value: string; - original: StripFlags; - } - - interface CommentStatement extends Statement { - type: 'CommentStatement'; - value: string; - strip: StripFlags; - } - - interface Expression extends Node {} - - interface SubExpression extends Expression { - type: 'SubExpression'; - path: PathExpression; - params: Expression[]; - hash: Hash; - } - - interface PathExpression extends Expression { - type: 'PathExpression'; - data: boolean; - depth: number; - parts: string[]; - original: string; - } - - interface Literal extends Expression {} - interface StringLiteral extends Literal { - type: 'StringLiteral'; - value: string; - original: string; - } - - interface BooleanLiteral extends Literal { - type: 'BooleanLiteral'; - value: boolean; - original: boolean; - } - - interface NumberLiteral extends Literal { - type: 'NumberLiteral'; - value: number; - original: number; - } - - interface UndefinedLiteral extends Literal { - type: 'UndefinedLiteral'; - } - - interface NullLiteral extends Literal { - type: 'NullLiteral'; - } - - interface Hash extends Node { - type: 'Hash'; - pairs: HashPair[]; - } - - interface HashPair extends Node { - type: 'HashPair'; - key: string; - value: Expression; - } - - interface StripFlags { - open: boolean; - close: boolean; - } - - interface helpers { - helperExpression(node: Node): boolean; - scopeId(path: PathExpression): boolean; - simpleId(path: PathExpression): boolean; - } - } -} - -declare module "handlebars" { - export = Handlebars; -} +export type CompilerInfo = [number/* revision */, string /* versions */]; declare module "handlebars/runtime" { export = Handlebars; } + +export default Handlebars; diff --git a/types/test.ts b/types/test.ts index 1ed038f6a..2010b89f8 100644 --- a/types/test.ts +++ b/types/test.ts @@ -4,7 +4,8 @@ * https://github.com/DefinitelyTyped/DefinitelyTyped/commits/1ce60bdc07f10e0b076778c6c953271c072bc894/types/handlebars/handlebars-tests.ts */ -import * as Handlebars from 'handlebars'; +import Handlebars from 'handlebars'; +import { HandlebarsTemplateDelegate, hbs } from 'handlebars'; const context = { author: { firstName: 'Alan', lastName: 'Johnson' }, diff --git a/types/tslint.json b/types/tslint.json index a484bf6b0..19dfb3eb4 100644 --- a/types/tslint.json +++ b/types/tslint.json @@ -1,5 +1,5 @@ { - "extends": "dtslint/dtslint.json", + "extends": "@definitelytyped/dtslint/dtslint.json", "rules": { "adjacent-overload-signatures": false, "array-type": false, @@ -76,4 +76,4 @@ "void-return": false, "whitespace": false } -} \ No newline at end of file +}